Skip to content

Commit e67937e

Browse files
authored
Merge pull request #5680 from stsewd/remove-files-after-build
Remove files after build
2 parents 22e85cb + 2d045fa commit e67937e

File tree

6 files changed

+147
-72
lines changed

6 files changed

+147
-72
lines changed

readthedocs/core/utils/__init__.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
"""Common utilty functions."""
22

3-
from __future__ import absolute_import
4-
53
import errno
64
import logging
75
import os
86
import re
97

8+
from celery import chord, group
109
from django.conf import settings
1110
from django.utils.functional import keep_lazy
1211
from django.utils.safestring import SafeText, mark_safe
1312
from django.utils.text import slugify as slugify_base
14-
from celery import group, chord
1513

1614
from readthedocs.builds.constants import BUILD_STATE_TRIGGERED
1715
from readthedocs.doc_builder.constants import DOCKER_LIMITS
1816

17+
1918
log = logging.getLogger(__name__)
2019

2120

readthedocs/projects/models.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -1389,6 +1389,7 @@ def add_features(sender, **kwargs):
13891389
USE_TESTING_BUILD_IMAGE = 'use_testing_build_image'
13901390
SHARE_SPHINX_DOCTREE = 'share_sphinx_doctree'
13911391
DEFAULT_TO_MKDOCS_0_17_3 = 'default_to_mkdocs_0_17_3'
1392+
CLEAN_AFTER_BUILD = 'clean_after_build'
13921393

13931394
FEATURES = (
13941395
(USE_SPHINX_LATEST, _('Use latest version of Sphinx')),
@@ -1423,7 +1424,11 @@ def add_features(sender, **kwargs):
14231424
),
14241425
(
14251426
DEFAULT_TO_MKDOCS_0_17_3,
1426-
_('Install mkdocs 0.17.3 by default')
1427+
_('Install mkdocs 0.17.3 by default'),
1428+
),
1429+
(
1430+
CLEAN_AFTER_BUILD,
1431+
_('Clean all files used in the build process'),
14271432
),
14281433
)
14291434

readthedocs/projects/tasks.py

+39-5
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
)
6161
from readthedocs.doc_builder.loader import get_builder_class
6262
from readthedocs.doc_builder.python_environments import Conda, Virtualenv
63-
from readthedocs.projects.models import APIProject
63+
from readthedocs.projects.models import APIProject, Feature
6464
from readthedocs.sphinx_domains.models import SphinxDomain
6565
from readthedocs.vcs_support import utils as vcs_support_utils
6666
from readthedocs.worker import app
@@ -203,8 +203,11 @@ def validate_duplicate_reserved_versions(self, data):
203203
@app.task(max_retries=5, default_retry_delay=7 * 60)
204204
def sync_repository_task(version_pk):
205205
"""Celery task to trigger VCS version sync."""
206-
step = SyncRepositoryTaskStep()
207-
return step.run(version_pk)
206+
try:
207+
step = SyncRepositoryTaskStep()
208+
return step.run(version_pk)
209+
finally:
210+
clean_build(version_pk)
208211

209212

210213
class SyncRepositoryTaskStep(SyncRepositoryMixin):
@@ -280,8 +283,11 @@ def run(self, version_pk): # pylint: disable=arguments-differ
280283
),
281284
)
282285
def update_docs_task(self, version_pk, *args, **kwargs):
283-
step = UpdateDocsTaskStep(task=self)
284-
return step.run(version_pk, *args, **kwargs)
286+
try:
287+
step = UpdateDocsTaskStep(task=self)
288+
return step.run(version_pk, *args, **kwargs)
289+
finally:
290+
clean_build(version_pk)
285291

286292

287293
class UpdateDocsTaskStep(SyncRepositoryMixin):
@@ -1379,6 +1385,34 @@ def warn(self, msg):
13791385
delete_queryset.delete()
13801386

13811387

1388+
def clean_build(version_pk):
1389+
"""Clean the files used in the build of the given version."""
1390+
version = Version.objects.get_object_or_log(pk=version_pk)
1391+
if (
1392+
not version or
1393+
not version.project.has_feature(Feature.CLEAN_AFTER_BUILD)
1394+
):
1395+
log.info(
1396+
'Skipping build files deletetion for version: %s',
1397+
version_pk,
1398+
)
1399+
return False
1400+
# NOTE: we are skipping the deletion of the `artifacts` dir
1401+
# because we are syncing the servers with an async task.
1402+
del_dirs = [
1403+
os.path.join(version.project.doc_path, dir_, version.slug)
1404+
for dir_ in ('checkouts', 'envs', 'conda')
1405+
]
1406+
try:
1407+
with version.project.repo_nonblockinglock(version):
1408+
log.info('Removing: %s', del_dirs)
1409+
remove_dirs(del_dirs)
1410+
except vcs_support_utils.LockTimeout:
1411+
log.info('Another task is running. Not removing: %s', del_dirs)
1412+
else:
1413+
return True
1414+
1415+
13821416
def _manage_imported_files(version, path, commit):
13831417
"""
13841418
Update imported files for version.

readthedocs/rtd_tests/tests/test_celery.py

+56
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,68 @@ def test_no_notification_on_version_locked_error(self, mock_setup_vcs, mock_send
159159
mock_send_notifications.assert_not_called()
160160
self.assertTrue(result.successful())
161161

162+
@patch('readthedocs.projects.tasks.UpdateDocsTaskStep.setup_python_environment', new=MagicMock)
163+
@patch('readthedocs.projects.tasks.UpdateDocsTaskStep.setup_vcs', new=MagicMock)
164+
@patch('readthedocs.doc_builder.environments.BuildEnvironment.update_build', new=MagicMock)
165+
@patch('readthedocs.projects.tasks.clean_build')
166+
@patch('readthedocs.projects.tasks.UpdateDocsTaskStep.build_docs')
167+
def test_clean_build_after_update_docs(self, build_docs, clean_build):
168+
version = self.project.versions.first()
169+
build = get(
170+
Build, project=self.project,
171+
version=version,
172+
)
173+
with mock_api(self.repo) as mapi:
174+
result = tasks.update_docs_task.delay(
175+
version.pk,
176+
build_pk=build.pk,
177+
record=False,
178+
intersphinx=False,
179+
)
180+
self.assertTrue(result.successful())
181+
clean_build.assert_called_with(version.pk)
182+
183+
@patch('readthedocs.projects.tasks.clean_build')
184+
@patch('readthedocs.projects.tasks.UpdateDocsTaskStep.run_setup')
185+
def test_clean_build_after_failure_in_update_docs(self, run_setup, clean_build):
186+
run_setup.side_effect = Exception()
187+
version = self.project.versions.first()
188+
build = get(
189+
Build, project=self.project,
190+
version=version,
191+
)
192+
with mock_api(self.repo):
193+
result = tasks.update_docs_task.delay(
194+
version.pk,
195+
build_pk=build.pk,
196+
record=False,
197+
intersphinx=False,
198+
)
199+
clean_build.assert_called_with(version.pk)
200+
162201
def test_sync_repository(self):
163202
version = self.project.versions.get(slug=LATEST)
164203
with mock_api(self.repo):
165204
result = tasks.sync_repository_task.delay(version.pk)
166205
self.assertTrue(result.successful())
167206

207+
@patch('readthedocs.projects.tasks.clean_build')
208+
def test_clean_build_after_sync_repository(self, clean_build):
209+
version = self.project.versions.get(slug=LATEST)
210+
with mock_api(self.repo):
211+
result = tasks.sync_repository_task.delay(version.pk)
212+
self.assertTrue(result.successful())
213+
clean_build.assert_called_with(version.pk)
214+
215+
@patch('readthedocs.projects.tasks.SyncRepositoryTaskStep.run')
216+
@patch('readthedocs.projects.tasks.clean_build')
217+
def test_clean_build_after_failure_in_sync_repository(self, clean_build, run_syn_repository):
218+
run_syn_repository.side_effect = Exception()
219+
version = self.project.versions.get(slug=LATEST)
220+
with mock_api(self.repo):
221+
result = tasks.sync_repository_task.delay(version.pk)
222+
clean_build.assert_called_with(version.pk)
223+
168224
@patch('readthedocs.projects.tasks.api_v2')
169225
@patch('readthedocs.projects.models.Project.checkout_path')
170226
def test_check_duplicate_reserved_version_latest(self, checkout_path, api_v2):

readthedocs/rtd_tests/tests/test_core_utils.py

+42-58
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
"""Test core util functions."""
22

33
import os
4-
import mock
54

6-
from mock import call
5+
import mock
76
from django.http import Http404
87
from django.test import TestCase
98
from django_dynamic_fixture import get
9+
from mock import call
1010

11+
from readthedocs.builds.constants import LATEST
1112
from readthedocs.builds.models import Version
12-
from readthedocs.core.utils.general import wipe_version_via_slugs
13-
from readthedocs.projects.tasks import remove_dirs
1413
from readthedocs.core.utils import slugify, trigger_build
14+
from readthedocs.core.utils.general import wipe_version_via_slugs
1515
from readthedocs.projects.models import Project
16-
from readthedocs.builds.constants import LATEST
16+
from readthedocs.projects.tasks import remove_dirs
1717

1818

1919
class CoreUtilTests(TestCase):
@@ -54,15 +54,13 @@ def test_trigger_build_when_version_not_provided_default_version_exist(self, upd
5454
'build_pk': mock.ANY,
5555
}
5656

57-
update_docs_task.signature.assert_has_calls([
58-
mock.call(
59-
args=(version_1.pk,),
60-
kwargs=kwargs,
61-
options=mock.ANY,
62-
immutable=True,
63-
),
64-
])
65-
57+
update_docs_task.signature.assert_called_with(
58+
args=(version_1.pk,),
59+
kwargs=kwargs,
60+
options=mock.ANY,
61+
immutable=True,
62+
)
63+
6664
@mock.patch('readthedocs.projects.tasks.update_docs_task')
6765
def test_trigger_build_when_version_not_provided_default_version_doesnt_exist(self, update_docs_task):
6866

@@ -78,14 +76,12 @@ def test_trigger_build_when_version_not_provided_default_version_doesnt_exist(se
7876
'build_pk': mock.ANY,
7977
}
8078

81-
update_docs_task.signature.assert_has_calls([
82-
mock.call(
83-
args=(version.pk,),
84-
kwargs=kwargs,
85-
options=mock.ANY,
86-
immutable=True,
87-
),
88-
])
79+
update_docs_task.signature.assert_called_with(
80+
args=(version.pk,),
81+
kwargs=kwargs,
82+
options=mock.ANY,
83+
immutable=True,
84+
)
8985

9086
@mock.patch('readthedocs.projects.tasks.update_docs_task')
9187
def test_trigger_custom_queue(self, update_docs):
@@ -102,15 +98,12 @@ def test_trigger_custom_queue(self, update_docs):
10298
'time_limit': 720,
10399
'soft_time_limit': 600,
104100
}
105-
update_docs.signature.assert_has_calls([
106-
mock.call(
107-
args=(self.version.pk,),
108-
kwargs=kwargs,
109-
options=options,
110-
immutable=True,
111-
),
112-
])
113-
update_docs.signature().apply_async.assert_called()
101+
update_docs.signature.assert_called_with(
102+
args=(self.version.pk,),
103+
kwargs=kwargs,
104+
options=options,
105+
immutable=True,
106+
)
114107

115108
@mock.patch('readthedocs.projects.tasks.update_docs_task')
116109
def test_trigger_build_time_limit(self, update_docs):
@@ -126,15 +119,12 @@ def test_trigger_build_time_limit(self, update_docs):
126119
'time_limit': 720,
127120
'soft_time_limit': 600,
128121
}
129-
update_docs.signature.assert_has_calls([
130-
mock.call(
131-
args=(self.version.pk,),
132-
kwargs=kwargs,
133-
options=options,
134-
immutable=True,
135-
),
136-
])
137-
update_docs.signature().apply_async.assert_called()
122+
update_docs.signature.assert_called_with(
123+
args=(self.version.pk,),
124+
kwargs=kwargs,
125+
options=options,
126+
immutable=True,
127+
)
138128

139129
@mock.patch('readthedocs.projects.tasks.update_docs_task')
140130
def test_trigger_build_invalid_time_limit(self, update_docs):
@@ -151,15 +141,12 @@ def test_trigger_build_invalid_time_limit(self, update_docs):
151141
'time_limit': 720,
152142
'soft_time_limit': 600,
153143
}
154-
update_docs.signature.assert_has_calls([
155-
mock.call(
156-
args=(self.version.pk,),
157-
kwargs=kwargs,
158-
options=options,
159-
immutable=True,
160-
),
161-
])
162-
update_docs.signature().apply_async.assert_called()
144+
update_docs.signature.assert_called_with(
145+
args=(self.version.pk,),
146+
kwargs=kwargs,
147+
options=options,
148+
immutable=True,
149+
)
163150

164151
@mock.patch('readthedocs.projects.tasks.update_docs_task')
165152
def test_trigger_build_rounded_time_limit(self, update_docs):
@@ -176,15 +163,12 @@ def test_trigger_build_rounded_time_limit(self, update_docs):
176163
'time_limit': 3,
177164
'soft_time_limit': 3,
178165
}
179-
update_docs.signature.assert_has_calls([
180-
mock.call(
181-
args=(self.version.pk,),
182-
kwargs=kwargs,
183-
options=options,
184-
immutable=True,
185-
),
186-
])
187-
update_docs.signature().apply_async.assert_called()
166+
update_docs.signature.assert_called_with(
167+
args=(self.version.pk,),
168+
kwargs=kwargs,
169+
options=options,
170+
immutable=True,
171+
)
188172

189173
def test_slugify(self):
190174
"""Test additional slugify."""

readthedocs/rtd_tests/tests/test_privacy.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
# -*- coding: utf-8 -*-
2-
import json
31
import logging
42

53
import mock
@@ -9,14 +7,15 @@
97

108
from readthedocs.builds.constants import LATEST
119
from readthedocs.builds.models import Build, Version
12-
from readthedocs.projects import tasks
1310
from readthedocs.projects.forms import UpdateProjectForm
1411
from readthedocs.projects.models import Project
1512

1613

1714
log = logging.getLogger(__name__)
1815

1916

17+
@mock.patch('readthedocs.projects.tasks.clean_build', new=mock.MagicMock)
18+
@mock.patch('readthedocs.projects.tasks.update_docs_task.signature', new=mock.MagicMock)
2019
class PrivacyTests(TestCase):
2120

2221
def setUp(self):
@@ -28,8 +27,6 @@ def setUp(self):
2827
self.tester.set_password('test')
2928
self.tester.save()
3029

31-
tasks.update_docs_task.delay = mock.Mock()
32-
3330
def _create_kong(
3431
self, privacy_level='private',
3532
version_privacy_level='private',

0 commit comments

Comments
 (0)