From f4a14422261e0950dbb04a64b1259fb9a44af443 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 11 Mar 2024 16:42:37 +0100 Subject: [PATCH] Build: remove `append_conf` _magic_ from MkDocs Delete all the magic around MkDocs YAML that processes the `mkdocs.yml`. file and define `readthedocs` theme automatically based on a feature flag, `MKDOCS_THEME_RTD`. This PR removes: - automatically defining `readthedocs` as theme when `MKDOCS_THEME_RTD` feature flag is defined. - processing `mkdocs.yml` to add internal JS and CSS (embed and analytics) files automatically This is another step forward on removing all the magic we perform on behalf the users and being more explicit about how to configure each doctool. Reference: * https://github.com/readthedocs/addons/issues/72#issuecomment-1926647293 Closes #8529 --- readthedocs/config/tests/test_yaml_loader.py | 11 +- readthedocs/doc_builder/backends/mkdocs.py | 246 +-------- readthedocs/projects/tests/mockers.py | 3 - .../rtd_tests/tests/test_doc_builder.py | 506 +----------------- 4 files changed, 17 insertions(+), 749 deletions(-) diff --git a/readthedocs/config/tests/test_yaml_loader.py b/readthedocs/config/tests/test_yaml_loader.py index ff74f2827f9..f5fe980b09b 100644 --- a/readthedocs/config/tests/test_yaml_loader.py +++ b/readthedocs/config/tests/test_yaml_loader.py @@ -1,8 +1,4 @@ -from readthedocs.doc_builder.backends.mkdocs import ( - ProxyPythonName, - yaml_dump_safely, - yaml_load_safely, -) +from readthedocs.doc_builder.backends.mkdocs import ProxyPythonName, yaml_load_safely content = """ int: 3 @@ -30,8 +26,3 @@ def test_yaml_load_safely(): assert type(data["other_function"]) is ProxyPythonName assert data["function"].value == "python_function" assert data["other_function"].value == "module.other.function" - - -def test_yaml_dump_safely(): - data = yaml_load_safely(content) - assert yaml_load_safely(yaml_dump_safely(data)) == data diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index cd4a256e0b1..16276a9dcd2 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -9,14 +9,10 @@ import structlog import yaml from django.conf import settings -from django.template import loader as template_loader from readthedocs.core.utils.filesystem import safe_open from readthedocs.doc_builder.base import BaseBuilder -from readthedocs.doc_builder.exceptions import MkDocsYAMLParseError from readthedocs.projects.constants import MKDOCS, MKDOCS_HTML -from readthedocs.projects.exceptions import UserFileNotFound -from readthedocs.projects.models import Feature log = structlog.get_logger(__name__) @@ -49,31 +45,23 @@ def __init__(self, *args, **kwargs): # This is the *MkDocs* yaml file self.yaml_file = self.get_yaml_config() - # README: historically, the default theme was ``readthedocs`` but in - # https://github.com/rtfd/readthedocs.org/pull/4556 we change it to - # ``mkdocs`` to maintain the same behavior in Read the Docs than - # building locally. Although, we can't apply this into the Corporate - # site. To keep the same default theme there, we created a Feature flag - # for these project that were building with MkDocs in the Corporate - # site. - if self.project.has_feature(Feature.MKDOCS_THEME_RTD): - self.DEFAULT_THEME_NAME = "readthedocs" - log.warning( - "Project using readthedocs theme as default for MkDocs.", - project_slug=self.project.slug, - ) - else: - self.DEFAULT_THEME_NAME = "mkdocs" - def get_final_doctype(self): """ Select a doctype based on the ``use_directory_urls`` setting. https://www.mkdocs.org/user-guide/configuration/#use_directory_urls """ + + # TODO: we should eventually remove this method completely and stop + # relying on "loading the `mkdocs.yml` file in a safe way just to know + # if it's a MKDOCS or MKDOCS_HTML documentation type". + # Allow symlinks, but only the ones that resolve inside the base directory. with safe_open( - self.yaml_file, "r", allow_symlinks=True, base_path=self.project_path + self.yaml_file, + "r", + allow_symlinks=True, + base_path=self.project_path, ) as fh: config = yaml_load_safely(fh) use_directory_urls = config.get("use_directory_urls", True) @@ -89,135 +77,16 @@ def get_yaml_config(self): mkdocs_path, ) - def load_yaml_config(self): - """ - Load a YAML config. - - :raises: ``MkDocsYAMLParseError`` if failed due to syntax errors. - """ - try: - # Allow symlinks, but only the ones that resolve inside the base directory. - result = safe_open( - self.yaml_file, "r", allow_symlinks=True, base_path=self.project_path - ) - if not result: - raise UserFileNotFound( - message_id=UserFileNotFound.FILE_NOT_FOUND, - format_values={ - "filename": self.yaml_file, - }, - ) - - config = yaml_load_safely(result) - - if not config: - raise MkDocsYAMLParseError(MkDocsYAMLParseError.EMPTY_CONFIG) - if not isinstance(config, dict): - raise MkDocsYAMLParseError(MkDocsYAMLParseError.CONFIG_NOT_DICT) - return config - - except IOError: - raise MkDocsYAMLParseError(MkDocsYAMLParseError.NOT_FOUND) - except yaml.YAMLError as exc: - note = "" - if hasattr(exc, "problem_mark"): - mark = exc.problem_mark - note = " (line %d, column %d)" % ( - mark.line + 1, - mark.column + 1, - ) - raise MkDocsYAMLParseError( - MkDocsYAMLParseError.SYNTAX_ERROR, - ) from exc - def append_conf(self): """ - Set mkdocs config values. + Call `cat mkdocs.yaml` only. - :raises: ``MkDocsYAMLParseError`` if failed due to known type errors - (i.e. expecting a list and a string is found). - """ - user_config = self.load_yaml_config() - - # Handle custom docs dirs - docs_dir = user_config.get("docs_dir", "docs") - if not isinstance(docs_dir, (type(None), str)): - raise MkDocsYAMLParseError( - MkDocsYAMLParseError.INVALID_DOCS_DIR_CONFIG, - ) - - user_config["docs_dir"] = docs_dir - static_url = self.project.proxied_static_path - - # Set mkdocs config values. - extra_assets = { - "extra_javascript": [ - "readthedocs-data.js", - f"{static_url}core/js/readthedocs-doc-embed.js", - f"{static_url}javascript/readthedocs-analytics.js", - ], - "extra_css": [ - f"{static_url}css/badge_only.css", - f"{static_url}css/readthedocs-doc-embed.css", - ], - } - - for config, extras in extra_assets.items(): - value = user_config.get(config, []) - if value is None: - value = [] - if not isinstance(value, list): - raise MkDocsYAMLParseError( - message_id=MkDocsYAMLParseError.INVALID_EXTRA_CONFIG, - format_values={ - "extra_config": config, - }, - ) - # Add the static file only if isn't already in the list. - value.extend([extra for extra in extras if extra not in value]) - user_config[config] = value - - # The docs path is relative to the location - # of the mkdocs configuration file. - docs_path = os.path.join( - os.path.dirname(self.yaml_file), - docs_dir, - ) - - # if user puts an invalid `docs_dir` path raise an Exception - if not os.path.exists(docs_path): - raise MkDocsYAMLParseError( - MkDocsYAMLParseError.INVALID_DOCS_DIR_PATH, - ) - - # RTD javascript writing - rtd_data = self.generate_rtd_data( - docs_dir=os.path.relpath(docs_path, self.project_path), - mkdocs_config=user_config, - ) - with safe_open( - os.path.join(docs_path, "readthedocs-data.js"), "w", encoding="utf-8" - ) as f: - f.write(rtd_data) - - # Use Read the Docs' analytics setup rather than mkdocs' - # This supports using RTD's privacy improvements around analytics - user_config["google_analytics"] = None - - # README: make MkDocs to use ``readthedocs`` theme as default if the - # user didn't specify a specific theme manually - if self.project.has_feature(Feature.MKDOCS_THEME_RTD): - if "theme" not in user_config: - # mkdocs<0.17 syntax - user_config["theme"] = self.DEFAULT_THEME_NAME - - # Write the modified mkdocs configuration - with safe_open(self.yaml_file, "w", encoding="utf-8") as f: - yaml_dump_safely( - user_config, - f, - ) + This behavior has changed. We used to parse the YAML file and append + some configs automatically, but we have been removing that magic from + our builders as much as we can. + This method will eventually removed completely. + """ # Write the mkdocs.yml to the build logs self.run( "cat", @@ -225,56 +94,6 @@ def append_conf(self): cwd=self.project_path, ) - def generate_rtd_data(self, docs_dir, mkdocs_config): - """Generate template properties and render readthedocs-data.js.""" - # Use the analytics code from mkdocs.yml - # if it isn't set already by Read the Docs, - analytics_code = self.version.project.analytics_code - if not analytics_code and mkdocs_config.get("google_analytics"): - # http://www.mkdocs.org/user-guide/configuration/#google_analytics - analytics_code = mkdocs_config["google_analytics"][0] - - commit = ( - self.version.project.vcs_repo( - version=self.version.slug, - environment=self.build_env, - ).commit, - ) - - # Will be available in the JavaScript as READTHEDOCS_DATA. - readthedocs_data = { - "project": self.version.project.slug, - "version": self.version.slug, - "language": self.version.project.language, - "programming_language": self.version.project.programming_language, - "page": None, - "theme": self.get_theme_name(mkdocs_config), - "builder": "mkdocs", - "docroot": docs_dir, - "source_suffix": ".md", - "api_host": settings.PUBLIC_API_URL, - "ad_free": not self.project.show_advertising, - "commit": commit, - "global_analytics_code": ( - None - if self.project.analytics_disabled - else settings.GLOBAL_ANALYTICS_CODE - ), - "user_analytics_code": analytics_code, - "proxied_static_path": self.project.proxied_static_path, - "proxied_api_host": self.project.proxied_api_host, - } - - data_ctx = { - "readthedocs_data": readthedocs_data, - "current_version": readthedocs_data["version"], - "slug": readthedocs_data["project"], - "html_theme": readthedocs_data["theme"], - "pagename": None, - } - tmpl = template_loader.get_template("doc_builder/data.js.tmpl") - return tmpl.render(data_ctx) - def build(self): build_command = [ self.python_env.venv_bin(filename="python"), @@ -296,42 +115,12 @@ def build(self): ) return cmd_ret.successful - def get_theme_name(self, mkdocs_config): - """ - Get the theme configuration in the mkdocs_config. - - In v0.17.0, the theme configuration switched - from two separate configs (both optional) to a nested directive. - - :see: http://www.mkdocs.org/about/release-notes/#theme-customization-1164 - :returns: the name of the theme RTD will use - """ - theme_setting = mkdocs_config.get("theme") - if isinstance(theme_setting, dict): - # Full nested theme config (the new configuration) - return theme_setting.get("name") or self.DEFAULT_THEME_NAME - - if theme_setting: - # A string which is the name of the theme - return theme_setting - - theme_dir = mkdocs_config.get("theme_dir") - if theme_dir: - # Use the name of the directory in this project's custom theme directory - return theme_dir.rstrip("/").split("/")[-1] - - return self.DEFAULT_THEME_NAME - class MkdocsHTML(BaseMkdocs): builder = "build" build_dir = "_readthedocs/html" -# TODO: find a better way to integrate with MkDocs. -# See https://github.com/readthedocs/readthedocs.org/issues/7844 - - class ProxyPythonName(yaml.YAMLObject): def __init__(self, value): self.value = value @@ -389,8 +178,3 @@ def yaml_load_safely(content): information loss. """ return yaml.load(content, Loader=SafeLoader) - - -def yaml_dump_safely(content, stream=None): - """Uses ``SafeDumper`` dumper to write YAML contents.""" - return yaml.dump(content, stream=stream, Dumper=SafeDumper) diff --git a/readthedocs/projects/tests/mockers.py b/readthedocs/projects/tests/mockers.py index 4dae7d137de..2f15f3ebb3e 100644 --- a/readthedocs/projects/tests/mockers.py +++ b/readthedocs/projects/tests/mockers.py @@ -108,9 +108,6 @@ def _mock_artifact_builders(self): "readthedocs.doc_builder.backends.sphinx.HtmlBuilder.append_conf", ) - # self.patches['builder.html.mkdocs.yaml_dump_safely'] = mock.patch( - # 'readthedocs.doc_builder.backends.mkdocs.yaml_dump_safely', - # ) # self.patches['builder.html.mkdocs.open'] = mock.patch( # 'readthedocs.doc_builder.backends.mkdocs.builtins.open', # mock.mock_open(read_data='file content'), diff --git a/readthedocs/rtd_tests/tests/test_doc_builder.py b/readthedocs/rtd_tests/tests/test_doc_builder.py index 40367c2cffd..d21d72be8eb 100644 --- a/readthedocs/rtd_tests/tests/test_doc_builder.py +++ b/readthedocs/rtd_tests/tests/test_doc_builder.py @@ -5,24 +5,14 @@ import py import pytest -import yaml from django.test import TestCase from django.test.utils import override_settings -from django_dynamic_fixture import get -from readthedocs.builds.models import Version from readthedocs.config.tests.test_config import get_build_config -from readthedocs.doc_builder.backends.mkdocs import ( - MkdocsHTML, - SafeDumper, - yaml_load_safely, -) from readthedocs.doc_builder.backends.sphinx import BaseSphinx -from readthedocs.doc_builder.environments import LocalBuildEnvironment -from readthedocs.doc_builder.exceptions import MkDocsYAMLParseError from readthedocs.doc_builder.python_environments import Virtualenv from readthedocs.projects.exceptions import ProjectConfigurationError -from readthedocs.projects.models import Feature, Project +from readthedocs.projects.models import Project @override_settings(PRODUCTION_DOMAIN="readthedocs.org") @@ -163,497 +153,3 @@ def test_multiple_conf_py( with pytest.raises(ProjectConfigurationError): with override_settings(DOCROOT=tmp_docs_dir): base_sphinx.append_conf() - - -@override_settings(PRODUCTION_DOMAIN="readthedocs.org") -class MkdocsBuilderTest(TestCase): - def setUp(self): - self.project = get(Project, documentation_type="mkdocs", name="mkdocs") - self.version = get(Version, project=self.project) - - self.build_env = LocalBuildEnvironment(api_client=mock.MagicMock()) - self.build_env.project = self.project - self.build_env.version = self.version - - @patch("readthedocs.projects.models.Project.checkout_path") - def test_get_theme_name(self, checkout_path): - tmpdir = tempfile.mkdtemp() - checkout_path.return_value = tmpdir - python_env = Virtualenv( - version=self.version, - build_env=self.build_env, - config=get_build_config( - {"mkdocs": {"configuration": "mkdocs.yml"}}, validate=True - ), - ) - builder = MkdocsHTML( - build_env=self.build_env, - python_env=python_env, - ) - - # The default theme is mkdocs but in mkdocs>=1.0, theme is required - self.assertEqual(builder.get_theme_name({}), "mkdocs") - - # mkdocs<0.17 syntax - config = { - "theme": "readthedocs", - } - self.assertEqual(builder.get_theme_name(config), "readthedocs") - - # mkdocs>=0.17 syntax - config = { - "theme": { - "name": "test_theme", - }, - } - self.assertEqual(builder.get_theme_name(config), "test_theme") - - # No theme but just a directory - config = { - "theme_dir": "/path/to/mydir", - } - self.assertEqual(builder.get_theme_name(config), "mydir") - config = { - "theme_dir": "/path/to/mydir/", - } - self.assertEqual(builder.get_theme_name(config), "mydir") - - @patch("readthedocs.doc_builder.base.BaseBuilder.run") - @patch("readthedocs.projects.models.Project.checkout_path") - def test_get_theme_name_with_feature_flag(self, checkout_path, run): - tmpdir = tempfile.mkdtemp() - checkout_path.return_value = tmpdir - - feature = get( - Feature, - feature_id=Feature.MKDOCS_THEME_RTD, - ) - feature.projects.add(self.project) - - python_env = Virtualenv( - version=self.version, - build_env=self.build_env, - config=get_build_config( - {"mkdocs": {"configuration": "mkdocs.yml"}}, validate=True - ), - ) - builder = MkdocsHTML( - build_env=self.build_env, - python_env=python_env, - ) - self.assertEqual(builder.get_theme_name({}), "readthedocs") - with patch("readthedocs.doc_builder.backends.mkdocs.yaml") as mock_yaml: - with patch( - "readthedocs.doc_builder.backends.mkdocs.MkdocsHTML.load_yaml_config" - ) as mock_load_yaml_config: - mock_load_yaml_config.return_value = { - "site_name": self.project.name, - "docs_dir": tmpdir, - } - with override_settings(DOCROOT=tmpdir): - builder.append_conf() - - mock_yaml.dump.assert_called_once_with( - { - "site_name": mock.ANY, - "docs_dir": mock.ANY, - "extra_javascript": mock.ANY, - "extra_css": mock.ANY, - "google_analytics": mock.ANY, - "theme": "readthedocs", - }, - stream=mock.ANY, - Dumper=SafeDumper, - ) - mock_yaml.reset_mock() - - config = { - "theme": "customtheme", - } - self.assertEqual(builder.get_theme_name(config), "customtheme") - with patch( - "readthedocs.doc_builder.backends.mkdocs.MkdocsHTML.load_yaml_config" - ) as mock_load_yaml_config: - mock_load_yaml_config.return_value = { - "site_name": self.project.name, - "theme": "customtheme", - "docs_dir": tmpdir, - } - with override_settings(DOCROOT=tmpdir): - builder.append_conf() - - mock_yaml.dump.assert_called_once_with( - { - "site_name": mock.ANY, - "docs_dir": mock.ANY, - "extra_javascript": mock.ANY, - "extra_css": mock.ANY, - "google_analytics": mock.ANY, - "theme": "customtheme", - }, - stream=mock.ANY, - Dumper=SafeDumper, - ) - - @patch("readthedocs.doc_builder.base.BaseBuilder.run") - @patch("readthedocs.projects.models.Project.checkout_path") - def test_append_conf_existing_yaml_on_root(self, checkout_path, run): - tmpdir = tempfile.mkdtemp() - os.mkdir(os.path.join(tmpdir, "docs")) - yaml_file = os.path.join(tmpdir, "mkdocs.yml") - yaml.safe_dump( - { - "site_name": "mkdocs", - "google_analytics": ["UA-1234-5", "mkdocs.org"], - "docs_dir": "docs", - }, - open(yaml_file, "w"), - ) - checkout_path.return_value = tmpdir - - python_env = Virtualenv( - version=self.version, - build_env=self.build_env, - config=get_build_config( - {"mkdocs": {"configuration": "mkdocs.yml"}}, validate=True - ), - ) - self.searchbuilder = MkdocsHTML( - build_env=self.build_env, - python_env=python_env, - ) - with override_settings(DOCROOT=tmpdir): - self.searchbuilder.append_conf() - - run.assert_called_with("cat", "mkdocs.yml", cwd=mock.ANY) - - config = yaml_load_safely(open(yaml_file)) - self.assertEqual( - config["docs_dir"], - "docs", - ) - self.assertEqual( - config["extra_css"], - [ - "/_/static/css/badge_only.css", - "/_/static/css/readthedocs-doc-embed.css", - ], - ) - self.assertEqual( - config["extra_javascript"], - [ - "readthedocs-data.js", - "/_/static/core/js/readthedocs-doc-embed.js", - "/_/static/javascript/readthedocs-analytics.js", - ], - ) - self.assertIsNone( - config["google_analytics"], - ) - self.assertEqual( - config["site_name"], - "mkdocs", - ) - - @patch("readthedocs.doc_builder.base.BaseBuilder.run") - @patch("readthedocs.projects.models.Project.checkout_path") - def test_append_conf_existing_yaml_on_root_with_invalid_setting( - self, checkout_path, run - ): - tmpdir = tempfile.mkdtemp() - os.mkdir(os.path.join(tmpdir, "docs")) - yaml_file = os.path.join(tmpdir, "mkdocs.yml") - checkout_path.return_value = tmpdir - - python_env = Virtualenv( - version=self.version, - build_env=self.build_env, - config=get_build_config( - {"mkdocs": {"configuration": "mkdocs.yml"}}, validate=True - ), - ) - self.searchbuilder = MkdocsHTML( - build_env=self.build_env, - python_env=python_env, - ) - - # We can't use ``@pytest.mark.parametrize`` on a Django test case - yaml_contents = [ - {"docs_dir": ["docs"]}, - {"extra_css": "a string here"}, - {"extra_javascript": ""}, - ] - for content in yaml_contents: - yaml.safe_dump( - content, - open(yaml_file, "w"), - ) - with self.assertRaises(MkDocsYAMLParseError): - with override_settings(DOCROOT=tmpdir): - self.searchbuilder.append_conf() - - @patch("readthedocs.doc_builder.base.BaseBuilder.run") - @patch("readthedocs.projects.models.Project.checkout_path") - def test_append_conf_and_none_values(self, checkout_path, run): - tmpdir = tempfile.mkdtemp() - os.mkdir(os.path.join(tmpdir, "docs")) - yaml_file = os.path.join(tmpdir, "mkdocs.yml") - checkout_path.return_value = tmpdir - - python_env = Virtualenv( - version=self.version, - build_env=self.build_env, - config=get_build_config( - {"mkdocs": {"configuration": "mkdocs.yml"}}, validate=True - ), - ) - builder = MkdocsHTML( - build_env=self.build_env, - python_env=python_env, - ) - - yaml.safe_dump( - { - "extra_css": None, - "extra_javascript": None, - }, - open(yaml_file, "w"), - ) - with override_settings(DOCROOT=tmpdir): - builder.append_conf() - config = yaml_load_safely(open(yaml_file)) - - self.assertEqual( - config["extra_css"], - [ - "/_/static/css/badge_only.css", - "/_/static/css/readthedocs-doc-embed.css", - ], - ) - self.assertEqual( - config["extra_javascript"], - [ - "readthedocs-data.js", - "/_/static/core/js/readthedocs-doc-embed.js", - "/_/static/javascript/readthedocs-analytics.js", - ], - ) - - @patch("readthedocs.doc_builder.base.BaseBuilder.run") - @patch("readthedocs.projects.models.Project.checkout_path") - def test_dont_override_theme(self, checkout_path, run): - tmpdir = tempfile.mkdtemp() - os.mkdir(os.path.join(tmpdir, "docs")) - yaml_file = os.path.join(tmpdir, "mkdocs.yml") - yaml.safe_dump( - { - "theme": "not-readthedocs", - "theme_dir": "not-readthedocs", - "site_name": "mkdocs", - "docs_dir": "docs", - }, - open(yaml_file, "w"), - ) - checkout_path.return_value = tmpdir - - python_env = Virtualenv( - version=self.version, - build_env=self.build_env, - config=get_build_config( - {"mkdocs": {"configuration": "mkdocs.yml"}}, validate=True - ), - ) - self.searchbuilder = MkdocsHTML( - build_env=self.build_env, - python_env=python_env, - ) - with override_settings(DOCROOT=tmpdir): - self.searchbuilder.append_conf() - - run.assert_called_with("cat", "mkdocs.yml", cwd=mock.ANY) - - config = yaml_load_safely(open(yaml_file)) - self.assertEqual( - config["theme_dir"], - "not-readthedocs", - ) - - @patch("readthedocs.doc_builder.backends.mkdocs.BaseMkdocs.generate_rtd_data") - @patch("readthedocs.doc_builder.base.BaseBuilder.run") - @patch("readthedocs.projects.models.Project.checkout_path") - def test_write_js_data_docs_dir(self, checkout_path, run, generate_rtd_data): - tmpdir = tempfile.mkdtemp() - os.mkdir(os.path.join(tmpdir, "docs")) - yaml_file = os.path.join(tmpdir, "mkdocs.yml") - yaml.safe_dump( - { - "site_name": "mkdocs", - "docs_dir": "docs", - }, - open(yaml_file, "w"), - ) - checkout_path.return_value = tmpdir - generate_rtd_data.return_value = "" - - python_env = Virtualenv( - version=self.version, - build_env=self.build_env, - config=get_build_config( - {"mkdocs": {"configuration": "mkdocs.yml"}}, validate=True - ), - ) - self.searchbuilder = MkdocsHTML( - build_env=self.build_env, - python_env=python_env, - ) - with override_settings(DOCROOT=tmpdir): - self.searchbuilder.append_conf() - - generate_rtd_data.assert_called_with( - docs_dir="docs", - mkdocs_config=mock.ANY, - ) - - @patch("readthedocs.doc_builder.backends.mkdocs.BaseMkdocs.generate_rtd_data") - @patch("readthedocs.projects.models.Project.checkout_path") - def test_write_js_data_on_invalid_docs_dir(self, checkout_path, generate_rtd_data): - tmpdir = tempfile.mkdtemp() - os.mkdir(os.path.join(tmpdir, "docs")) - yaml_file = os.path.join(tmpdir, "mkdocs.yml") - yaml.safe_dump( - { - "site_name": "mkdocs", - "google_analytics": ["UA-1234-5", "mkdocs.org"], - "docs_dir": "invalid_docs_dir", - "extra_css": ["http://readthedocs.org/static/css/badge_only.css"], - "extra_javascript": ["readthedocs-data.js"], - }, - open(yaml_file, "w"), - ) - checkout_path.return_value = tmpdir - - python_env = Virtualenv( - version=self.version, - build_env=self.build_env, - config=get_build_config( - {"mkdocs": {"configuration": "mkdocs.yml"}}, validate=True - ), - ) - self.searchbuilder = MkdocsHTML( - build_env=self.build_env, - python_env=python_env, - ) - with self.assertRaises(MkDocsYAMLParseError): - with override_settings(DOCROOT=tmpdir): - self.searchbuilder.append_conf() - - @patch("readthedocs.doc_builder.base.BaseBuilder.run") - @patch("readthedocs.projects.models.Project.checkout_path") - def test_append_conf_existing_yaml_with_extra(self, checkout_path, run): - tmpdir = tempfile.mkdtemp() - os.mkdir(os.path.join(tmpdir, "docs")) - yaml_file = os.path.join(tmpdir, "mkdocs.yml") - yaml.safe_dump( - { - "site_name": "mkdocs", - "google_analytics": ["UA-1234-5", "mkdocs.org"], - "docs_dir": "docs", - "extra_css": ["/_/static/css/badge_only.css"], - "extra_javascript": ["readthedocs-data.js"], - }, - open(yaml_file, "w"), - ) - checkout_path.return_value = tmpdir - - python_env = Virtualenv( - version=self.version, - build_env=self.build_env, - config=get_build_config( - {"mkdocs": {"configuration": "mkdocs.yml"}}, validate=True - ), - ) - self.searchbuilder = MkdocsHTML( - build_env=self.build_env, - python_env=python_env, - ) - with override_settings(DOCROOT=tmpdir): - self.searchbuilder.append_conf() - - run.assert_called_with("cat", "mkdocs.yml", cwd=mock.ANY) - - config = yaml_load_safely(open(yaml_file)) - - self.assertEqual( - config["extra_css"], - [ - "/_/static/css/badge_only.css", - "/_/static/css/readthedocs-doc-embed.css", - ], - ) - self.assertEqual( - config["extra_javascript"], - [ - "readthedocs-data.js", - "/_/static/core/js/readthedocs-doc-embed.js", - "/_/static/javascript/readthedocs-analytics.js", - ], - ) - - @patch("readthedocs.projects.models.Project.checkout_path") - def test_empty_yaml_config(self, checkout_path): - tmpdir = tempfile.mkdtemp() - os.mkdir(os.path.join(tmpdir, "docs")) - yaml_file = os.path.join(tmpdir, "mkdocs.yml") - yaml.safe_dump( - "", - open(yaml_file, "w"), - ) - checkout_path.return_value = tmpdir - - python_env = Virtualenv( - version=self.version, - build_env=self.build_env, - config=get_build_config( - {"mkdocs": {"configuration": "mkdocs.yml"}}, validate=True - ), - ) - self.searchbuilder = MkdocsHTML( - build_env=self.build_env, - python_env=python_env, - ) - - with self.assertRaises(MkDocsYAMLParseError) as exc: - with override_settings(DOCROOT=tmpdir): - self.searchbuilder.append_conf() - self.assertEqual(exc.exception.message_id, MkDocsYAMLParseError.EMPTY_CONFIG) - - @patch("readthedocs.projects.models.Project.checkout_path") - def test_yaml_config_not_returns_dict(self, checkout_path): - tmpdir = tempfile.mkdtemp() - os.mkdir(os.path.join(tmpdir, "docs")) - yaml_file = os.path.join(tmpdir, "mkdocs.yml") - yaml.safe_dump( - "test_string", - open(yaml_file, "w"), - ) - checkout_path.return_value = tmpdir - - python_env = Virtualenv( - version=self.version, - build_env=self.build_env, - config=get_build_config( - {"mkdocs": {"configuration": "mkdocs.yml"}}, validate=True - ), - ) - self.searchbuilder = MkdocsHTML( - build_env=self.build_env, - python_env=python_env, - ) - - with self.assertRaises(MkDocsYAMLParseError) as e: - with override_settings(DOCROOT=tmpdir): - self.searchbuilder.append_conf() - self.assertEqual( - e.exception.message_id, - MkDocsYAMLParseError.CONFIG_NOT_DICT, - )