##// END OF EJS Templates
summary: use non-memory cache for readme, and cleanup cache for repo stats.
marcink -
r3892:8afbef06 default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,376 +1,374 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import string
23 23 import time
24 24
25 25 import rhodecode
26 26
27 27 from pyramid.view import view_config
28 28
29 29 from rhodecode.lib.view_utils import get_format_ref_id
30 30 from rhodecode.apps._base import RepoAppView
31 31 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
32 32 from rhodecode.lib import helpers as h, rc_cache
33 33 from rhodecode.lib.utils2 import safe_str, safe_int
34 34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 35 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
36 36 from rhodecode.lib.ext_json import json
37 37 from rhodecode.lib.vcs.backends.base import EmptyCommit
38 38 from rhodecode.lib.vcs.exceptions import (
39 39 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
40 40 from rhodecode.model.db import Statistics, CacheKey, User
41 41 from rhodecode.model.meta import Session
42 42 from rhodecode.model.repo import ReadmeFinder
43 43 from rhodecode.model.scm import ScmModel
44 44
45 45 log = logging.getLogger(__name__)
46 46
47 47
48 48 class RepoSummaryView(RepoAppView):
49 49
50 50 def load_default_context(self):
51 51 c = self._get_local_tmpl_context(include_app_defaults=True)
52 52 c.rhodecode_repo = None
53 53 if not c.repository_requirements_missing:
54 54 c.rhodecode_repo = self.rhodecode_vcs_repo
55 55 return c
56 56
57 57 def _get_readme_data(self, db_repo, renderer_type):
58 58 log.debug('Looking for README file')
59 59 landing_commit = db_repo.get_landing_commit()
60 60 if isinstance(landing_commit, EmptyCommit):
61 61 return None, None
62 62
63 cache_namespace_uid = 'cache_repo_instance.{}_{}'.format(
64 db_repo.repo_id, CacheKey.CACHE_TYPE_README)
65 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
63 cache_namespace_uid = 'cache_repo.{}'.format(db_repo.repo_id)
64 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
66 65 start = time.time()
67 66
68 67 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
69 68 def generate_repo_readme(repo_id, commit_id, _repo_name, _renderer_type):
70 69 readme_data = None
71 70 readme_filename = None
72 71
73 72 commit = db_repo.get_commit(commit_id)
74 73 log.debug("Searching for a README file at commit %s.", commit_id)
75 74 readme_node = ReadmeFinder(_renderer_type).search(commit)
76 75
77 76 if readme_node:
78 77 log.debug('Found README node: %s', readme_node)
79 78 relative_urls = {
80 79 'raw': h.route_path(
81 80 'repo_file_raw', repo_name=_repo_name,
82 81 commit_id=commit.raw_id, f_path=readme_node.path),
83 82 'standard': h.route_path(
84 83 'repo_files', repo_name=_repo_name,
85 84 commit_id=commit.raw_id, f_path=readme_node.path),
86 85 }
87 86 readme_data = self._render_readme_or_none(commit, readme_node, relative_urls)
88 87 readme_filename = readme_node.unicode_path
89 88
90 89 return readme_data, readme_filename
91 90
92 91 readme_data, readme_filename = generate_repo_readme(
93 92 db_repo.repo_id, landing_commit.raw_id, db_repo.repo_name, renderer_type,)
94 93 compute_time = time.time() - start
95 94 log.debug('Repo readme generated and computed in %.4fs', compute_time)
96 95 return readme_data, readme_filename
97 96
98 97 def _render_readme_or_none(self, commit, readme_node, relative_urls):
99 98 log.debug('Found README file `%s` rendering...', readme_node.path)
100 99 renderer = MarkupRenderer()
101 100 try:
102 101 html_source = renderer.render(
103 102 readme_node.content, filename=readme_node.path)
104 103 if relative_urls:
105 104 return relative_links(html_source, relative_urls)
106 105 return html_source
107 106 except Exception:
108 107 log.exception(
109 108 "Exception while trying to render the README")
110 109
111 110 def _load_commits_context(self, c):
112 111 p = safe_int(self.request.GET.get('page'), 1)
113 112 size = safe_int(self.request.GET.get('size'), 10)
114 113
115 114 def url_generator(**kw):
116 115 query_params = {
117 116 'size': size
118 117 }
119 118 query_params.update(kw)
120 119 return h.route_path(
121 120 'repo_summary_commits',
122 121 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
123 122
124 123 pre_load = ['author', 'branch', 'date', 'message']
125 124 try:
126 125 collection = self.rhodecode_vcs_repo.get_commits(
127 126 pre_load=pre_load, translate_tags=False)
128 127 except EmptyRepositoryError:
129 128 collection = self.rhodecode_vcs_repo
130 129
131 130 c.repo_commits = h.RepoPage(
132 131 collection, page=p, items_per_page=size, url=url_generator)
133 132 page_ids = [x.raw_id for x in c.repo_commits]
134 133 c.comments = self.db_repo.get_comments(page_ids)
135 134 c.statuses = self.db_repo.statuses(page_ids)
136 135
137 136 def _prepare_and_set_clone_url(self, c):
138 137 username = ''
139 138 if self._rhodecode_user.username != User.DEFAULT_USER:
140 139 username = safe_str(self._rhodecode_user.username)
141 140
142 141 _def_clone_uri = _def_clone_uri_id = c.clone_uri_tmpl
143 142 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
144 143
145 144 if '{repo}' in _def_clone_uri:
146 145 _def_clone_uri_id = _def_clone_uri.replace('{repo}', '_{repoid}')
147 146 elif '{repoid}' in _def_clone_uri:
148 147 _def_clone_uri_id = _def_clone_uri.replace('_{repoid}', '{repo}')
149 148
150 149 c.clone_repo_url = self.db_repo.clone_url(
151 150 user=username, uri_tmpl=_def_clone_uri)
152 151 c.clone_repo_url_id = self.db_repo.clone_url(
153 152 user=username, uri_tmpl=_def_clone_uri_id)
154 153 c.clone_repo_url_ssh = self.db_repo.clone_url(
155 154 uri_tmpl=_def_clone_uri_ssh, ssh=True)
156 155
157 156 @LoginRequired()
158 157 @HasRepoPermissionAnyDecorator(
159 158 'repository.read', 'repository.write', 'repository.admin')
160 159 @view_config(
161 160 route_name='repo_summary_commits', request_method='GET',
162 161 renderer='rhodecode:templates/summary/summary_commits.mako')
163 162 def summary_commits(self):
164 163 c = self.load_default_context()
165 164 self._prepare_and_set_clone_url(c)
166 165 self._load_commits_context(c)
167 166 return self._get_template_context(c)
168 167
169 168 @LoginRequired()
170 169 @HasRepoPermissionAnyDecorator(
171 170 'repository.read', 'repository.write', 'repository.admin')
172 171 @view_config(
173 172 route_name='repo_summary', request_method='GET',
174 173 renderer='rhodecode:templates/summary/summary.mako')
175 174 @view_config(
176 175 route_name='repo_summary_slash', request_method='GET',
177 176 renderer='rhodecode:templates/summary/summary.mako')
178 177 @view_config(
179 178 route_name='repo_summary_explicit', request_method='GET',
180 179 renderer='rhodecode:templates/summary/summary.mako')
181 180 def summary(self):
182 181 c = self.load_default_context()
183 182
184 183 # Prepare the clone URL
185 184 self._prepare_and_set_clone_url(c)
186 185
187 186 # update every 5 min
188 187 if self.db_repo.last_commit_cache_update_diff > 60 * 5:
189 188 self.db_repo.update_commit_cache()
190 189
191 190 # If enabled, get statistics data
192 191
193 192 c.show_stats = bool(self.db_repo.enable_statistics)
194 193
195 194 stats = Session().query(Statistics) \
196 195 .filter(Statistics.repository == self.db_repo) \
197 196 .scalar()
198 197
199 198 c.stats_percentage = 0
200 199
201 200 if stats and stats.languages:
202 201 c.no_data = False is self.db_repo.enable_statistics
203 202 lang_stats_d = json.loads(stats.languages)
204 203
205 204 # Sort first by decreasing count and second by the file extension,
206 205 # so we have a consistent output.
207 206 lang_stats_items = sorted(lang_stats_d.iteritems(),
208 207 key=lambda k: (-k[1], k[0]))[:10]
209 208 lang_stats = [(x, {"count": y,
210 209 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
211 210 for x, y in lang_stats_items]
212 211
213 212 c.trending_languages = json.dumps(lang_stats)
214 213 else:
215 214 c.no_data = True
216 215 c.trending_languages = json.dumps({})
217 216
218 217 scm_model = ScmModel()
219 218 c.enable_downloads = self.db_repo.enable_downloads
220 219 c.repository_followers = scm_model.get_followers(self.db_repo)
221 220 c.repository_forks = scm_model.get_forks(self.db_repo)
222 221
223 222 # first interaction with the VCS instance after here...
224 223 if c.repository_requirements_missing:
225 224 self.request.override_renderer = \
226 225 'rhodecode:templates/summary/missing_requirements.mako'
227 226 return self._get_template_context(c)
228 227
229 228 c.readme_data, c.readme_file = \
230 229 self._get_readme_data(self.db_repo, c.visual.default_renderer)
231 230
232 231 # loads the summary commits template context
233 232 self._load_commits_context(c)
234 233
235 234 return self._get_template_context(c)
236 235
237 def get_request_commit_id(self):
238 return self.request.matchdict['commit_id']
239
240 236 @LoginRequired()
241 237 @HasRepoPermissionAnyDecorator(
242 238 'repository.read', 'repository.write', 'repository.admin')
243 239 @view_config(
244 240 route_name='repo_stats', request_method='GET',
245 241 renderer='json_ext')
246 242 def repo_stats(self):
247 commit_id = self.get_request_commit_id()
248 243 show_stats = bool(self.db_repo.enable_statistics)
249 244 repo_id = self.db_repo.repo_id
250 245
251 cache_seconds = safe_int(
252 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
246 landing_commit = self.db_repo.get_landing_commit()
247 if isinstance(landing_commit, EmptyCommit):
248 return {'size': 0, 'code_stats': {}}
249
250 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
253 251 cache_on = cache_seconds > 0
252
254 253 log.debug(
255 'Computing REPO TREE for repo_id %s commit_id `%s` '
254 'Computing REPO STATS for repo_id %s commit_id `%s` '
256 255 'with caching: %s[TTL: %ss]' % (
257 repo_id, commit_id, cache_on, cache_seconds or 0))
256 repo_id, landing_commit, cache_on, cache_seconds or 0))
258 257
259 258 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
260 259 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
261 260
262 261 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
263 262 condition=cache_on)
264 def compute_stats(repo_id, commit_id, show_stats):
263 def compute_stats(repo_id, commit_id, _show_stats):
265 264 code_stats = {}
266 265 size = 0
267 266 try:
268 scm_instance = self.db_repo.scm_instance()
269 commit = scm_instance.get_commit(commit_id)
267 commit = self.db_repo.get_commit(commit_id)
270 268
271 269 for node in commit.get_filenodes_generator():
272 270 size += node.size
273 if not show_stats:
271 if not _show_stats:
274 272 continue
275 273 ext = string.lower(node.extension)
276 274 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
277 275 if ext_info:
278 276 if ext in code_stats:
279 277 code_stats[ext]['count'] += 1
280 278 else:
281 279 code_stats[ext] = {"count": 1, "desc": ext_info}
282 280 except (EmptyRepositoryError, CommitDoesNotExistError):
283 281 pass
284 282 return {'size': h.format_byte_size_binary(size),
285 283 'code_stats': code_stats}
286 284
287 stats = compute_stats(self.db_repo.repo_id, commit_id, show_stats)
285 stats = compute_stats(self.db_repo.repo_id, landing_commit.raw_id, show_stats)
288 286 return stats
289 287
290 288 @LoginRequired()
291 289 @HasRepoPermissionAnyDecorator(
292 290 'repository.read', 'repository.write', 'repository.admin')
293 291 @view_config(
294 292 route_name='repo_refs_data', request_method='GET',
295 293 renderer='json_ext')
296 294 def repo_refs_data(self):
297 295 _ = self.request.translate
298 296 self.load_default_context()
299 297
300 298 repo = self.rhodecode_vcs_repo
301 299 refs_to_create = [
302 300 (_("Branch"), repo.branches, 'branch'),
303 301 (_("Tag"), repo.tags, 'tag'),
304 302 (_("Bookmark"), repo.bookmarks, 'book'),
305 303 ]
306 304 res = self._create_reference_data(repo, self.db_repo_name, refs_to_create)
307 305 data = {
308 306 'more': False,
309 307 'results': res
310 308 }
311 309 return data
312 310
313 311 @LoginRequired()
314 312 @HasRepoPermissionAnyDecorator(
315 313 'repository.read', 'repository.write', 'repository.admin')
316 314 @view_config(
317 315 route_name='repo_refs_changelog_data', request_method='GET',
318 316 renderer='json_ext')
319 317 def repo_refs_changelog_data(self):
320 318 _ = self.request.translate
321 319 self.load_default_context()
322 320
323 321 repo = self.rhodecode_vcs_repo
324 322
325 323 refs_to_create = [
326 324 (_("Branches"), repo.branches, 'branch'),
327 325 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
328 326 # TODO: enable when vcs can handle bookmarks filters
329 327 # (_("Bookmarks"), repo.bookmarks, "book"),
330 328 ]
331 329 res = self._create_reference_data(
332 330 repo, self.db_repo_name, refs_to_create)
333 331 data = {
334 332 'more': False,
335 333 'results': res
336 334 }
337 335 return data
338 336
339 337 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
340 338 format_ref_id = get_format_ref_id(repo)
341 339
342 340 result = []
343 341 for title, refs, ref_type in refs_to_create:
344 342 if refs:
345 343 result.append({
346 344 'text': title,
347 345 'children': self._create_reference_items(
348 346 repo, full_repo_name, refs, ref_type,
349 347 format_ref_id),
350 348 })
351 349 return result
352 350
353 351 def _create_reference_items(self, repo, full_repo_name, refs, ref_type, format_ref_id):
354 352 result = []
355 353 is_svn = h.is_svn(repo)
356 354 for ref_name, raw_id in refs.iteritems():
357 355 files_url = self._create_files_url(
358 356 repo, full_repo_name, ref_name, raw_id, is_svn)
359 357 result.append({
360 358 'text': ref_name,
361 359 'id': format_ref_id(ref_name, raw_id),
362 360 'raw_id': raw_id,
363 361 'type': ref_type,
364 362 'files_url': files_url,
365 363 'idx': 0,
366 364 })
367 365 return result
368 366
369 367 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
370 368 use_commit_id = '/' in ref_name or is_svn
371 369 return h.route_path(
372 370 'repo_files',
373 371 repo_name=full_repo_name,
374 372 f_path=ref_name if is_svn else '',
375 373 commit_id=raw_id if use_commit_id else ref_name,
376 374 _query=dict(at=ref_name))
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,687 +1,687 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 Package for testing various lib/helper functions in rhodecode
24 24 """
25 25
26 26 import datetime
27 27 import string
28 28 import mock
29 29 import pytest
30 30
31 31 from rhodecode.tests import no_newline_id_generator
32 32 from rhodecode.tests.utils import run_test_concurrently
33 33
34 34 from rhodecode.lib import rc_cache
35 35 from rhodecode.lib.helpers import InitialsGravatar
36 36 from rhodecode.lib.utils2 import AttributeDict
37 37
38 38 from rhodecode.model.db import Repository, CacheKey
39 39
40 40
41 41 def _urls_for_proto(proto):
42 42 return [
43 43 ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
44 44 '%s://127.0.0.1' % proto),
45 45 ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
46 46 '%s://127.0.0.1' % proto),
47 47 ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
48 48 '%s://127.0.0.1' % proto),
49 49 ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'],
50 50 '%s://127.0.0.1:8080' % proto),
51 51 ('%s://domain.org' % proto, ['%s://' % proto, 'domain.org'],
52 52 '%s://domain.org' % proto),
53 53 ('%s://user:pass@domain.org:8080' % proto,
54 54 ['%s://' % proto, 'domain.org', '8080'],
55 55 '%s://domain.org:8080' % proto),
56 56 ]
57 57
58 58 TEST_URLS = _urls_for_proto('http') + _urls_for_proto('https')
59 59
60 60
61 61 @pytest.mark.parametrize("test_url, expected, expected_creds", TEST_URLS)
62 62 def test_uri_filter(test_url, expected, expected_creds):
63 63 from rhodecode.lib.utils2 import uri_filter
64 64 assert uri_filter(test_url) == expected
65 65
66 66
67 67 @pytest.mark.parametrize("test_url, expected, expected_creds", TEST_URLS)
68 68 def test_credentials_filter(test_url, expected, expected_creds):
69 69 from rhodecode.lib.utils2 import credentials_filter
70 70 assert credentials_filter(test_url) == expected_creds
71 71
72 72
73 73 @pytest.mark.parametrize("str_bool, expected", [
74 74 ('t', True),
75 75 ('true', True),
76 76 ('y', True),
77 77 ('yes', True),
78 78 ('on', True),
79 79 ('1', True),
80 80 ('Y', True),
81 81 ('yeS', True),
82 82 ('Y', True),
83 83 ('TRUE', True),
84 84 ('T', True),
85 85 ('False', False),
86 86 ('F', False),
87 87 ('FALSE', False),
88 88 ('0', False),
89 89 ('-1', False),
90 90 ('', False)
91 91 ])
92 92 def test_str2bool(str_bool, expected):
93 93 from rhodecode.lib.utils2 import str2bool
94 94 assert str2bool(str_bool) == expected
95 95
96 96
97 97 @pytest.mark.parametrize("text, expected", reduce(lambda a1,a2:a1+a2, [
98 98 [
99 99 (pref+"", []),
100 100 (pref+"Hi there @marcink", ['marcink']),
101 101 (pref+"Hi there @marcink and @bob", ['bob', 'marcink']),
102 102 (pref+"Hi there @marcink\n", ['marcink']),
103 103 (pref+"Hi there @marcink and @bob\n", ['bob', 'marcink']),
104 104 (pref+"Hi there marcin@rhodecode.com", []),
105 105 (pref+"Hi there @john.malcovic and @bob\n", ['bob', 'john.malcovic']),
106 106 (pref+"This needs to be reviewed: (@marcink,@john)", ["john", "marcink"]),
107 107 (pref+"This needs to be reviewed: (@marcink, @john)", ["john", "marcink"]),
108 108 (pref+"This needs to be reviewed: [@marcink,@john]", ["john", "marcink"]),
109 109 (pref+"This needs to be reviewed: (@marcink @john)", ["john", "marcink"]),
110 110 (pref+"@john @mary, please review", ["john", "mary"]),
111 111 (pref+"@john,@mary, please review", ["john", "mary"]),
112 112 (pref+"Hej @123, @22john,@mary, please review", ['123', '22john', 'mary']),
113 113 (pref+"@first hi there @marcink here's my email marcin@email.com "
114 114 "@lukaszb check @one_more22 it pls @ ttwelve @D[] @one@two@three ", ['first', 'lukaszb', 'marcink', 'one', 'one_more22']),
115 115 (pref+"@MARCIN @maRCiN @2one_more22 @john please see this http://org.pl", ['2one_more22', 'john', 'MARCIN', 'maRCiN']),
116 116 (pref+"@marian.user just do it @marco-polo and next extract @marco_polo", ['marco-polo', 'marco_polo', 'marian.user']),
117 117 (pref+"user.dot hej ! not-needed maril@domain.org", []),
118 118 (pref+"\n@marcin", ['marcin']),
119 119 ]
120 120 for pref in ['', '\n', 'hi !', '\t', '\n\n']]), ids=no_newline_id_generator)
121 121 def test_mention_extractor(text, expected):
122 122 from rhodecode.lib.utils2 import extract_mentioned_users
123 123 got = extract_mentioned_users(text)
124 124 assert sorted(got, key=lambda x: x.lower()) == got
125 125 assert set(expected) == set(got)
126 126
127 127 @pytest.mark.parametrize("age_args, expected, kw", [
128 128 ({}, u'just now', {}),
129 129 ({'seconds': -1}, u'1 second ago', {}),
130 130 ({'seconds': -60 * 2}, u'2 minutes ago', {}),
131 131 ({'hours': -1}, u'1 hour ago', {}),
132 132 ({'hours': -24}, u'1 day ago', {}),
133 133 ({'hours': -24 * 5}, u'5 days ago', {}),
134 134 ({'months': -1}, u'1 month ago', {}),
135 135 ({'months': -1, 'days': -2}, u'1 month and 2 days ago', {}),
136 136 ({'years': -1, 'months': -1}, u'1 year and 1 month ago', {}),
137 137 ({}, u'just now', {'short_format': True}),
138 138 ({'seconds': -1}, u'1sec ago', {'short_format': True}),
139 139 ({'seconds': -60 * 2}, u'2min ago', {'short_format': True}),
140 140 ({'hours': -1}, u'1h ago', {'short_format': True}),
141 141 ({'hours': -24}, u'1d ago', {'short_format': True}),
142 142 ({'hours': -24 * 5}, u'5d ago', {'short_format': True}),
143 143 ({'months': -1}, u'1m ago', {'short_format': True}),
144 144 ({'months': -1, 'days': -2}, u'1m, 2d ago', {'short_format': True}),
145 145 ({'years': -1, 'months': -1}, u'1y, 1m ago', {'short_format': True}),
146 146 ])
147 147 def test_age(age_args, expected, kw, baseapp):
148 148 from rhodecode.lib.utils2 import age
149 149 from dateutil import relativedelta
150 150 n = datetime.datetime(year=2012, month=5, day=17)
151 151 delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs)
152 152
153 153 def translate(elem):
154 154 return elem.interpolate()
155 155
156 156 assert translate(age(n + delt(**age_args), now=n, **kw)) == expected
157 157
158 158
159 159 @pytest.mark.parametrize("age_args, expected, kw", [
160 160 ({}, u'just now', {}),
161 161 ({'seconds': 1}, u'in 1 second', {}),
162 162 ({'seconds': 60 * 2}, u'in 2 minutes', {}),
163 163 ({'hours': 1}, u'in 1 hour', {}),
164 164 ({'hours': 24}, u'in 1 day', {}),
165 165 ({'hours': 24 * 5}, u'in 5 days', {}),
166 166 ({'months': 1}, u'in 1 month', {}),
167 167 ({'months': 1, 'days': 1}, u'in 1 month and 1 day', {}),
168 168 ({'years': 1, 'months': 1}, u'in 1 year and 1 month', {}),
169 169 ({}, u'just now', {'short_format': True}),
170 170 ({'seconds': 1}, u'in 1sec', {'short_format': True}),
171 171 ({'seconds': 60 * 2}, u'in 2min', {'short_format': True}),
172 172 ({'hours': 1}, u'in 1h', {'short_format': True}),
173 173 ({'hours': 24}, u'in 1d', {'short_format': True}),
174 174 ({'hours': 24 * 5}, u'in 5d', {'short_format': True}),
175 175 ({'months': 1}, u'in 1m', {'short_format': True}),
176 176 ({'months': 1, 'days': 1}, u'in 1m, 1d', {'short_format': True}),
177 177 ({'years': 1, 'months': 1}, u'in 1y, 1m', {'short_format': True}),
178 178 ])
179 179 def test_age_in_future(age_args, expected, kw, baseapp):
180 180 from rhodecode.lib.utils2 import age
181 181 from dateutil import relativedelta
182 182 n = datetime.datetime(year=2012, month=5, day=17)
183 183 delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs)
184 184
185 185 def translate(elem):
186 186 return elem.interpolate()
187 187
188 188 assert translate(age(n + delt(**age_args), now=n, **kw)) == expected
189 189
190 190
191 191 @pytest.mark.parametrize("sample, expected_tags", [
192 192 # entry
193 193 ((
194 194 ""
195 195 ),
196 196 [
197 197
198 198 ]),
199 199 # entry
200 200 ((
201 201 "hello world [stale]"
202 202 ),
203 203 [
204 204 ('state', '[stale]'),
205 205 ]),
206 206 # entry
207 207 ((
208 208 "hello world [v2.0.0] [v1.0.0]"
209 209 ),
210 210 [
211 211 ('generic', '[v2.0.0]'),
212 212 ('generic', '[v1.0.0]'),
213 213 ]),
214 214 # entry
215 215 ((
216 216 "he[ll]o wo[rl]d"
217 217 ),
218 218 [
219 219 ('label', '[ll]'),
220 220 ('label', '[rl]'),
221 221 ]),
222 222 # entry
223 223 ((
224 224 "hello world [stale]\n[featured]\n[stale] [dead] [dev]"
225 225 ),
226 226 [
227 227 ('state', '[stale]'),
228 228 ('state', '[featured]'),
229 229 ('state', '[stale]'),
230 230 ('state', '[dead]'),
231 231 ('state', '[dev]'),
232 232 ]),
233 233 # entry
234 234 ((
235 235 "hello world \n\n [stale] \n [url =&gt; [name](http://rc.com)]"
236 236 ),
237 237 [
238 238 ('state', '[stale]'),
239 239 ('url', '[url =&gt; [name](http://rc.com)]'),
240 240 ]),
241 241 # entry
242 242 ((
243 243 "[url =&gt; [linkNameJS](javascript:alert(document.domain))]\n"
244 244 "[url =&gt; [linkNameHTTP](http://rhodecode.com)]\n"
245 245 "[url =&gt; [linkNameHTTPS](https://rhodecode.com)]\n"
246 246 "[url =&gt; [linkNamePath](/repo_group)]\n"
247 247 ),
248 248 [
249 249 ('generic', '[linkNameJS]'),
250 250 ('url', '[url =&gt; [linkNameHTTP](http://rhodecode.com)]'),
251 251 ('url', '[url =&gt; [linkNameHTTPS](https://rhodecode.com)]'),
252 252 ('url', '[url =&gt; [linkNamePath](/repo_group)]'),
253 253 ]),
254 254 # entry
255 255 ((
256 256 "hello pta[tag] gog [[]] [[] sda ero[or]d [me =&gt;>< sa]"
257 257 "[requires] [stale] [see<>=&gt;] [see =&gt; http://url.com]"
258 258 "[requires =&gt; url] [lang =&gt; python] [just a tag] "
259 259 "<html_tag first='abc' attr=\"my.url?attr=&another=\"></html_tag>"
260 260 "[,d] [ =&gt; ULR ] [obsolete] [desc]]"
261 261 ),
262 262 [
263 263 ('label', '[desc]'),
264 264 ('label', '[obsolete]'),
265 265 ('label', '[or]'),
266 266 ('label', '[requires]'),
267 267 ('label', '[tag]'),
268 268 ('state', '[stale]'),
269 269 ('lang', '[lang =&gt; python]'),
270 270 ('ref', '[requires =&gt; url]'),
271 271 ('see', '[see =&gt; http://url.com]'),
272 272
273 273 ]),
274 274
275 275 ], ids=no_newline_id_generator)
276 276 def test_metatag_extraction(sample, expected_tags):
277 277 from rhodecode.lib.helpers import extract_metatags
278 278 tags, value = extract_metatags(sample)
279 279 assert sorted(tags) == sorted(expected_tags)
280 280
281 281
282 282 @pytest.mark.parametrize("tag_data, expected_html", [
283 283
284 284 (('state', '[stable]'), '<div class="metatag" tag="state stable">stable</div>'),
285 285 (('state', '[stale]'), '<div class="metatag" tag="state stale">stale</div>'),
286 286 (('state', '[featured]'), '<div class="metatag" tag="state featured">featured</div>'),
287 287 (('state', '[dev]'), '<div class="metatag" tag="state dev">dev</div>'),
288 288 (('state', '[dead]'), '<div class="metatag" tag="state dead">dead</div>'),
289 289
290 290 (('label', '[personal]'), '<div class="metatag" tag="label">personal</div>'),
291 291 (('generic', '[v2.0.0]'), '<div class="metatag" tag="generic">v2.0.0</div>'),
292 292
293 293 (('lang', '[lang =&gt; JavaScript]'), '<div class="metatag" tag="lang">JavaScript</div>'),
294 294 (('lang', '[lang =&gt; C++]'), '<div class="metatag" tag="lang">C++</div>'),
295 295 (('lang', '[lang =&gt; C#]'), '<div class="metatag" tag="lang">C#</div>'),
296 296 (('lang', '[lang =&gt; Delphi/Object]'), '<div class="metatag" tag="lang">Delphi/Object</div>'),
297 297 (('lang', '[lang =&gt; Objective-C]'), '<div class="metatag" tag="lang">Objective-C</div>'),
298 298 (('lang', '[lang =&gt; .NET]'), '<div class="metatag" tag="lang">.NET</div>'),
299 299
300 300 (('license', '[license =&gt; BSD 3-clause]'), '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/BSD 3-clause">BSD 3-clause</a></div>'),
301 301 (('license', '[license =&gt; GPLv3]'), '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/GPLv3">GPLv3</a></div>'),
302 302 (('license', '[license =&gt; MIT]'), '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/MIT">MIT</a></div>'),
303 303 (('license', '[license =&gt; AGPLv3]'), '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/AGPLv3">AGPLv3</a></div>'),
304 304
305 305 (('ref', '[requires =&gt; RepoName]'), '<div class="metatag" tag="ref requires">requires: <a href="/RepoName">RepoName</a></div>'),
306 306 (('ref', '[recommends =&gt; GroupName]'), '<div class="metatag" tag="ref recommends">recommends: <a href="/GroupName">GroupName</a></div>'),
307 307 (('ref', '[conflicts =&gt; SomeName]'), '<div class="metatag" tag="ref conflicts">conflicts: <a href="/SomeName">SomeName</a></div>'),
308 308 (('ref', '[base =&gt; SomeName]'), '<div class="metatag" tag="ref base">base: <a href="/SomeName">SomeName</a></div>'),
309 309
310 310 (('see', '[see =&gt; http://rhodecode.com]'), '<div class="metatag" tag="see">see: http://rhodecode.com </div>'),
311 311
312 312 (('url', '[url =&gt; [linkName](https://rhodecode.com)]'), '<div class="metatag" tag="url"> <a href="https://rhodecode.com">linkName</a> </div>'),
313 313 (('url', '[url =&gt; [example link](https://rhodecode.com)]'), '<div class="metatag" tag="url"> <a href="https://rhodecode.com">example link</a> </div>'),
314 314 (('url', '[url =&gt; [v1.0.0](https://rhodecode.com)]'), '<div class="metatag" tag="url"> <a href="https://rhodecode.com">v1.0.0</a> </div>'),
315 315
316 316 ])
317 317 def test_metatags_stylize(tag_data, expected_html):
318 318 from rhodecode.lib.helpers import style_metatag
319 319 tag_type,value = tag_data
320 320 assert style_metatag(tag_type, value) == expected_html
321 321
322 322
323 323 @pytest.mark.parametrize("tmpl_url, email, expected", [
324 324 ('http://test.com/{email}', 'test@foo.com', 'http://test.com/test@foo.com'),
325 325
326 326 ('http://test.com/{md5email}', 'test@foo.com', 'http://test.com/3cb7232fcc48743000cb86d0d5022bd9'),
327 327 ('http://test.com/{md5email}', 'testΔ…Δ‡@foo.com', 'http://test.com/978debb907a3c55cd741872ab293ef30'),
328 328
329 329 ('http://testX.com/{md5email}?s={size}', 'test@foo.com', 'http://testX.com/3cb7232fcc48743000cb86d0d5022bd9?s=24'),
330 330 ('http://testX.com/{md5email}?s={size}', 'testΔ…Δ‡@foo.com', 'http://testX.com/978debb907a3c55cd741872ab293ef30?s=24'),
331 331
332 332 ('{scheme}://{netloc}/{md5email}/{size}', 'test@foo.com', 'https://server.com/3cb7232fcc48743000cb86d0d5022bd9/24'),
333 333 ('{scheme}://{netloc}/{md5email}/{size}', 'testΔ…Δ‡@foo.com', 'https://server.com/978debb907a3c55cd741872ab293ef30/24'),
334 334
335 335 ('http://test.com/{email}', 'testΔ…Δ‡@foo.com', 'http://test.com/testΔ…Δ‡@foo.com'),
336 336 ('http://test.com/{email}?size={size}', 'test@foo.com', 'http://test.com/test@foo.com?size=24'),
337 337 ('http://test.com/{email}?size={size}', 'testΔ…Δ‡@foo.com', 'http://test.com/testΔ…Δ‡@foo.com?size=24'),
338 338 ])
339 339 def test_gravatar_url_builder(tmpl_url, email, expected, request_stub):
340 340 from rhodecode.lib.helpers import gravatar_url
341 341
342 342 def fake_tmpl_context(_url):
343 343 _c = AttributeDict()
344 344 _c.visual = AttributeDict()
345 345 _c.visual.use_gravatar = True
346 346 _c.visual.gravatar_url = _url
347 347 return _c
348 348
349 349 # mock pyramid.threadlocals
350 350 def fake_get_current_request():
351 351 request_stub.scheme = 'https'
352 352 request_stub.host = 'server.com'
353 353
354 354 request_stub._call_context = fake_tmpl_context(tmpl_url)
355 355 return request_stub
356 356
357 357 with mock.patch('rhodecode.lib.helpers.get_current_request',
358 358 fake_get_current_request):
359 359
360 360 grav = gravatar_url(email_address=email, size=24)
361 361 assert grav == expected
362 362
363 363
364 364 @pytest.mark.parametrize(
365 365 "email, first_name, last_name, expected_initials, expected_color", [
366 366
367 367 ('test@rhodecode.com', '', '', 'TR', '#8a994d'),
368 368 ('marcin.kuzminski@rhodecode.com', '', '', 'MK', '#6559b3'),
369 369 # special cases of email
370 370 ('john.van.dam@rhodecode.com', '', '', 'JD', '#526600'),
371 371 ('Guido.van.Rossum@rhodecode.com', '', '', 'GR', '#990052'),
372 372 ('Guido.van.Rossum@rhodecode.com', 'Guido', 'Van Rossum', 'GR', '#990052'),
373 373
374 374 ('rhodecode+Guido.van.Rossum@rhodecode.com', '', '', 'RR', '#46598c'),
375 375 ('pclouds@rhodecode.com', 'Nguyα»…n ThΓ‘i', 'Tgọc Duy', 'ND', '#665200'),
376 376
377 377 ('john-brown@foo.com', '', '', 'JF', '#73006b'),
378 378 ('admin@rhodecode.com', 'Marcin', 'Kuzminski', 'MK', '#104036'),
379 379 # partials
380 380 ('admin@rhodecode.com', 'Marcin', '', 'MR', '#104036'), # fn+email
381 381 ('admin@rhodecode.com', '', 'Kuzminski', 'AK', '#104036'), # em+ln
382 382 # non-ascii
383 383 ('admin@rhodecode.com', 'Marcin', 'Śuzminski', 'MS', '#104036'),
384 384 ('marcin.Ε›uzminski@rhodecode.com', '', '', 'MS', '#73000f'),
385 385
386 386 # special cases, LDAP can provide those...
387 387 ('admin@', 'Marcin', 'Śuzminski', 'MS', '#aa00ff'),
388 388 ('marcin.Ε›uzminski', '', '', 'MS', '#402020'),
389 389 ('null', '', '', 'NL', '#8c4646'),
390 390 ('some.@abc.com', 'some', '', 'SA', '#664e33')
391 391 ])
392 392 def test_initials_gravatar_pick_of_initials_and_color_algo(
393 393 email, first_name, last_name, expected_initials, expected_color):
394 394 instance = InitialsGravatar(email, first_name, last_name)
395 395 assert instance.get_initials() == expected_initials
396 396 assert instance.str2color(email) == expected_color
397 397
398 398
399 399 def test_initials_gravatar_mapping_algo():
400 400 pos = set()
401 401 instance = InitialsGravatar('', '', '')
402 402 iterations = 0
403 403
404 404 variations = []
405 405 for letter1 in string.ascii_letters:
406 406 for letter2 in string.ascii_letters[::-1][:10]:
407 407 for letter3 in string.ascii_letters[:10]:
408 408 variations.append(
409 409 '%s@rhodecode.com' % (letter1+letter2+letter3))
410 410
411 411 max_variations = 4096
412 412 for email in variations[:max_variations]:
413 413 iterations += 1
414 414 pos.add(
415 415 instance.pick_color_bank_index(email,
416 416 instance.get_color_bank()))
417 417
418 418 # we assume that we have match all 256 possible positions,
419 419 # in reasonable amount of different email addresses
420 420 assert len(pos) == 256
421 421 assert iterations == max_variations
422 422
423 423
424 424 @pytest.mark.parametrize("tmpl, repo_name, overrides, prefix, expected", [
425 425 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {}, '', 'http://vps1:8000/group/repo1'),
426 426 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/group/repo1'),
427 427 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {}, '/rc', 'http://vps1:8000/rc/group/repo1'),
428 428 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'user'}, '/rc', 'http://user@vps1:8000/rc/group/repo1'),
429 429 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '/rc', 'http://marcink@vps1:8000/rc/group/repo1'),
430 430 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'user'}, '/rc/', 'http://user@vps1:8000/rc/group/repo1'),
431 431 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '/rc/', 'http://marcink@vps1:8000/rc/group/repo1'),
432 432 ('{scheme}://{user}@{netloc}/_{repoid}', 'group/repo1', {}, '', 'http://vps1:8000/_23'),
433 433 ('{scheme}://{user}@{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/_23'),
434 434 ('http://{user}@{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/_23'),
435 435 ('http://{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://vps1:8000/_23'),
436 436 ('https://{user}@proxy1.server.com/{repo}', 'group/repo1', {'user': 'marcink'}, '', 'https://marcink@proxy1.server.com/group/repo1'),
437 437 ('https://{user}@proxy1.server.com/{repo}', 'group/repo1', {}, '', 'https://proxy1.server.com/group/repo1'),
438 438 ('https://proxy1.server.com/{user}/{repo}', 'group/repo1', {'user': 'marcink'}, '', 'https://proxy1.server.com/marcink/group/repo1'),
439 439 ])
440 440 def test_clone_url_generator(tmpl, repo_name, overrides, prefix, expected):
441 441 from rhodecode.lib.utils2 import get_clone_url
442 442
443 443 class RequestStub(object):
444 444 def request_url(self, name):
445 445 return 'http://vps1:8000' + prefix
446 446
447 447 def route_url(self, name):
448 448 return self.request_url(name)
449 449
450 450 clone_url = get_clone_url(
451 451 request=RequestStub(),
452 452 uri_tmpl=tmpl,
453 453 repo_name=repo_name, repo_id=23, **overrides)
454 454 assert clone_url == expected
455 455
456 456
457 457 def _quick_url(text, tmpl="""<a class="revision-link" href="%s">%s</a>""", url_=None):
458 458 """
459 459 Changes `some text url[foo]` => `some text <a href="/">foo</a>
460 460
461 461 :param text:
462 462 """
463 463 import re
464 464 # quickly change expected url[] into a link
465 465 URL_PAT = re.compile(r'(?:url\[)(.+?)(?:\])')
466 466
467 467 def url_func(match_obj):
468 468 _url = match_obj.groups()[0]
469 469 return tmpl % (url_ or '/some-url', _url)
470 470 return URL_PAT.sub(url_func, text)
471 471
472 472
473 473 @pytest.mark.parametrize("sample, expected", [
474 474 ("",
475 475 ""),
476 476 ("git-svn-id: https://svn.apache.org/repos/asf/libcloud/trunk@1441655 13f79535-47bb-0310-9956-ffa450edef68",
477 477 "git-svn-id: https://svn.apache.org/repos/asf/libcloud/trunk@1441655 13f79535-47bb-0310-9956-ffa450edef68"),
478 478 ("from rev 000000000000",
479 479 "from rev url[000000000000]"),
480 480 ("from rev 000000000000123123 also rev 000000000000",
481 481 "from rev url[000000000000123123] also rev url[000000000000]"),
482 482 ("this should-000 00",
483 483 "this should-000 00"),
484 484 ("longtextffffffffff rev 123123123123",
485 485 "longtextffffffffff rev url[123123123123]"),
486 486 ("rev ffffffffffffffffffffffffffffffffffffffffffffffffff",
487 487 "rev ffffffffffffffffffffffffffffffffffffffffffffffffff"),
488 488 ("ffffffffffff some text traalaa",
489 489 "url[ffffffffffff] some text traalaa"),
490 490 ("""Multi line
491 491 123123123123
492 492 some text 123123123123
493 493 sometimes !
494 494 """,
495 495 """Multi line
496 496 url[123123123123]
497 497 some text url[123123123123]
498 498 sometimes !
499 499 """)
500 500 ], ids=no_newline_id_generator)
501 501 def test_urlify_commits(sample, expected):
502 502 def fake_url(self, *args, **kwargs):
503 503 return '/some-url'
504 504
505 505 expected = _quick_url(expected)
506 506
507 507 with mock.patch('rhodecode.lib.helpers.route_url', fake_url):
508 508 from rhodecode.lib.helpers import urlify_commits
509 509 assert urlify_commits(sample, 'repo_name') == expected
510 510
511 511
512 512 @pytest.mark.parametrize("sample, expected, url_", [
513 513 ("",
514 514 "",
515 515 ""),
516 516 ("https://svn.apache.org/repos",
517 517 "url[https://svn.apache.org/repos]",
518 518 "https://svn.apache.org/repos"),
519 519 ("http://svn.apache.org/repos",
520 520 "url[http://svn.apache.org/repos]",
521 521 "http://svn.apache.org/repos"),
522 522 ("from rev a also rev http://google.com",
523 523 "from rev a also rev url[http://google.com]",
524 524 "http://google.com"),
525 525 ("""Multi line
526 526 https://foo.bar.com
527 527 some text lalala""",
528 528 """Multi line
529 529 url[https://foo.bar.com]
530 530 some text lalala""",
531 531 "https://foo.bar.com")
532 532 ], ids=no_newline_id_generator)
533 533 def test_urlify_test(sample, expected, url_):
534 534 from rhodecode.lib.helpers import urlify_text
535 535 expected = _quick_url(expected, tmpl="""<a href="%s">%s</a>""", url_=url_)
536 536 assert urlify_text(sample) == expected
537 537
538 538
539 539 @pytest.mark.parametrize("test, expected", [
540 540 ("", None),
541 541 ("/_2", '2'),
542 542 ("_2", '2'),
543 543 ("/_2/", '2'),
544 544 ("_2/", '2'),
545 545
546 546 ("/_21", '21'),
547 547 ("_21", '21'),
548 548 ("/_21/", '21'),
549 549 ("_21/", '21'),
550 550
551 551 ("/_21/foobar", '21'),
552 552 ("_21/121", '21'),
553 553 ("/_21/_12", '21'),
554 554 ("_21/rc/foo", '21'),
555 555
556 556 ])
557 557 def test_get_repo_by_id(test, expected):
558 558 from rhodecode.model.repo import RepoModel
559 559 _test = RepoModel()._extract_id_from_repo_name(test)
560 560 assert _test == expected
561 561
562 562
563 563 def test_invalidation_context(baseapp):
564 564 repo_id = 9999
565 565
566 566 cache_namespace_uid = 'cache_repo_instance.{}_{}'.format(
567 repo_id, CacheKey.CACHE_TYPE_README)
567 repo_id, CacheKey.CACHE_TYPE_FEED)
568 568 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
569 569 repo_id=repo_id)
570 570 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
571 571
572 572 calls = [1, 2]
573 573
574 574 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
575 575 def _dummy_func(cache_key):
576 576 val = calls.pop(0)
577 577 return 'result:{}'.format(val)
578 578
579 579 inv_context_manager = rc_cache.InvalidationContext(
580 580 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace)
581 581
582 582 # 1st call, fresh caches
583 583 with inv_context_manager as invalidation_context:
584 584 should_invalidate = invalidation_context.should_invalidate()
585 585 if should_invalidate:
586 586 result = _dummy_func.refresh('some-key')
587 587 else:
588 588 result = _dummy_func('some-key')
589 589
590 590 assert isinstance(invalidation_context, rc_cache.FreshRegionCache)
591 591 assert should_invalidate is True
592 592
593 593 assert 'result:1' == result
594 594 # should be cached so calling it twice will give the same result !
595 595 result = _dummy_func('some-key')
596 596 assert 'result:1' == result
597 597
598 598 # 2nd call, we create a new context manager, this should be now key aware, and
599 599 # return an active cache region
600 600 with inv_context_manager as invalidation_context:
601 601 should_invalidate = invalidation_context.should_invalidate()
602 602 assert isinstance(invalidation_context, rc_cache.ActiveRegionCache)
603 603 assert should_invalidate is False
604 604
605 605 # Mark invalidation
606 606 CacheKey.set_invalidate(invalidation_namespace)
607 607
608 608 # 3nd call, fresh caches
609 609 with inv_context_manager as invalidation_context:
610 610 should_invalidate = invalidation_context.should_invalidate()
611 611 if should_invalidate:
612 612 result = _dummy_func.refresh('some-key')
613 613 else:
614 614 result = _dummy_func('some-key')
615 615
616 616 assert isinstance(invalidation_context, rc_cache.FreshRegionCache)
617 617 assert should_invalidate is True
618 618
619 619 assert 'result:2' == result
620 620
621 621 # cached again, same result
622 622 result = _dummy_func('some-key')
623 623 assert 'result:2' == result
624 624
625 625
626 626 def test_invalidation_context_exception_in_compute(baseapp):
627 627 repo_id = 888
628 628
629 629 cache_namespace_uid = 'cache_repo_instance.{}_{}'.format(
630 repo_id, CacheKey.CACHE_TYPE_README)
630 repo_id, CacheKey.CACHE_TYPE_FEED)
631 631 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
632 632 repo_id=repo_id)
633 633 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
634 634
635 635 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
636 636 def _dummy_func(cache_key):
637 637 raise Exception('Error in cache func')
638 638
639 639 with pytest.raises(Exception):
640 640 inv_context_manager = rc_cache.InvalidationContext(
641 641 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace)
642 642
643 643 # 1st call, fresh caches
644 644 with inv_context_manager as invalidation_context:
645 645 should_invalidate = invalidation_context.should_invalidate()
646 646 if should_invalidate:
647 647 _dummy_func.refresh('some-key-2')
648 648 else:
649 649 _dummy_func('some-key-2')
650 650
651 651
652 652 @pytest.mark.parametrize('execution_number', range(5))
653 653 def test_cache_invalidation_race_condition(execution_number, baseapp):
654 654 import time
655 655
656 656 repo_id = 777
657 657
658 658 cache_namespace_uid = 'cache_repo_instance.{}_{}'.format(
659 repo_id, CacheKey.CACHE_TYPE_README)
659 repo_id, CacheKey.CACHE_TYPE_FEED)
660 660 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
661 661 repo_id=repo_id)
662 662 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
663 663
664 664 @run_test_concurrently(25)
665 665 def test_create_and_delete_cache_keys():
666 666 time.sleep(0.2)
667 667
668 668 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
669 669 def _dummy_func(cache_key):
670 670 val = 'async'
671 671 return 'result:{}'.format(val)
672 672
673 673 inv_context_manager = rc_cache.InvalidationContext(
674 674 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace)
675 675
676 676 # 1st call, fresh caches
677 677 with inv_context_manager as invalidation_context:
678 678 should_invalidate = invalidation_context.should_invalidate()
679 679 if should_invalidate:
680 680 _dummy_func.refresh('some-key-3')
681 681 else:
682 682 _dummy_func('some-key-3')
683 683
684 684 # Mark invalidation
685 685 CacheKey.set_invalidate(invalidation_namespace)
686 686
687 687 test_create_and_delete_cache_keys()
General Comments 0
You need to be logged in to leave comments. Login now