##// END OF EJS Templates
test: add warning about not mounting /tmp noexec
domruf -
r6570:3af2dea7 default
parent child Browse files
Show More
@@ -1,247 +1,252 b''
1 1 .. _contributing:
2 2
3 3 =========================
4 4 Contributing to Kallithea
5 5 =========================
6 6
7 7 Kallithea is developed and maintained by its users. Please join us and scratch
8 8 your own itch.
9 9
10 10
11 11 Infrastructure
12 12 --------------
13 13
14 14 The main repository is hosted on Our Own Kallithea (aka OOK) at
15 15 https://kallithea-scm.org/repos/kallithea/, our self-hosted instance
16 16 of Kallithea.
17 17
18 18 For now, we use Bitbucket_ for `pull requests`_ and `issue tracking`_. The
19 19 issue tracker is for tracking bugs, not for support, discussion, or ideas --
20 20 please use the `mailing list`_ or :ref:`IRC <readme>` to reach the community.
21 21
22 22 We use Weblate_ to translate the user interface messages into languages other
23 23 than English. Join our project on `Hosted Weblate`_ to help us.
24 24 To register, you can use your Bitbucket or GitHub account. See :ref:`translations`
25 25 for more details.
26 26
27 27
28 28 Getting started
29 29 ---------------
30 30
31 31 To get started with development::
32 32
33 33 hg clone https://kallithea-scm.org/repos/kallithea
34 34 cd kallithea
35 35 virtualenv ../kallithea-venv
36 36 source ../kallithea-venv/bin/activate
37 37 pip install --upgrade pip setuptools
38 38 pip install -e .
39 39 gearbox make-config my.ini
40 40 gearbox setup-db -c my.ini --user=user --email=user@example.com --password=password --repos=/tmp
41 41 gearbox serve -c my.ini --reload &
42 42 firefox http://127.0.0.1:5000/
43 43
44 44 You can also start out by forking https://bitbucket.org/conservancy/kallithea
45 45 on Bitbucket_ and create a local clone of your own fork.
46 46
47 47
48 48 Running tests
49 49 -------------
50 50
51 51 After finishing your changes make sure all tests pass cleanly. Install the test
52 52 dependencies, then run the testsuite by invoking ``py.test`` from the
53 53 project root::
54 54
55 55 pip install -r dev_requirements.txt
56 56 py.test
57 57
58 58 Note that testing on Python 2.6 also requires ``unittest2``.
59 59
60 Note that on unix systems, the temporary directory (``/tmp`` or where
61 ``$TMPDIR`` points) must allow executable files; Git hooks must be executable,
62 and the test suite creates repositories in the temporary directory. Linux
63 systems with /tmp mounted noexec will thus fail.
64
60 65 You can also use ``tox`` to run the tests with all supported Python versions
61 66 (currently Python 2.6--2.7).
62 67
63 68 When running tests, Kallithea uses `kallithea/tests/test.ini` and populates the
64 69 SQLite database specified there.
65 70
66 71 It is possible to avoid recreating the full test database on each invocation of
67 72 the tests, thus eliminating the initial delay. To achieve this, run the tests as::
68 73
69 74 gearbox serve -c kallithea/tests/test.ini --pid-file=test.pid --daemon
70 75 KALLITHEA_WHOOSH_TEST_DISABLE=1 KALLITHEA_NO_TMP_PATH=1 py.test
71 76 kill -9 $(cat test.pid)
72 77
73 78 In these commands, the following variables are used::
74 79
75 80 KALLITHEA_WHOOSH_TEST_DISABLE=1 - skip whoosh index building and tests
76 81 KALLITHEA_NO_TMP_PATH=1 - disable new temp path for tests, used mostly for testing_vcs_operations
77 82
78 83 You can run individual tests by specifying their path as argument to py.test.
79 84 py.test also has many more options, see `py.test -h`. Some useful options
80 85 are::
81 86
82 87 -k EXPRESSION only run tests which match the given substring
83 88 expression. An expression is a python evaluable
84 89 expression where all names are substring-matched
85 90 against test names and their parent classes. Example:
86 91 -x, --exitfirst exit instantly on first error or failed test.
87 92 --lf rerun only the tests that failed at the last run (or
88 93 all if none failed)
89 94 --ff run all tests but run the last failures first. This
90 95 may re-order tests and thus lead to repeated fixture
91 96 setup/teardown
92 97 --pdb start the interactive Python debugger on errors.
93 98 -s, --capture=no don't capture stdout (any stdout output will be
94 99 printed immediately)
95 100
96 101
97 102 Contribution guidelines
98 103 -----------------------
99 104
100 105 Kallithea is GPLv3 and we assume all contributions are made by the
101 106 committer/contributor and under GPLv3 unless explicitly stated. We do care a
102 107 lot about preservation of copyright and license information for existing code
103 108 that is brought into the project.
104 109
105 110 Contributions will be accepted in most formats -- such as pull requests on
106 111 Bitbucket, something hosted on your own Kallithea instance, or patches sent by
107 112 email to the `kallithea-general`_ mailing list.
108 113
109 114 When contributing via Bitbucket, please make your fork of
110 115 https://bitbucket.org/conservancy/kallithea/ `non-publishing`_ -- it is one of
111 116 the settings on "Repository details" page. This ensures your commits are in
112 117 "draft" phase and makes it easier for you to address feedback and for project
113 118 maintainers to integrate your changes.
114 119
115 120 .. _non-publishing: https://www.mercurial-scm.org/wiki/Phases#Publishing_Repository
116 121
117 122 Make sure to test your changes both manually and with the automatic tests
118 123 before posting.
119 124
120 125 We care about quality and review and keeping a clean repository history. We
121 126 might give feedback that requests polishing contributions until they are
122 127 "perfect". We might also rebase and collapse and make minor adjustments to your
123 128 changes when we apply them.
124 129
125 130 We try to make sure we have consensus on the direction the project is taking.
126 131 Everything non-sensitive should be discussed in public -- preferably on the
127 132 mailing list. We aim at having all non-trivial changes reviewed by at least
128 133 one other core developer before pushing. Obvious non-controversial changes will
129 134 be handled more casually.
130 135
131 136 For now we just have one official branch ("default") and will keep it so stable
132 137 that it can be (and is) used in production. Experimental changes should live
133 138 elsewhere (for example in a pull request) until they are ready.
134 139
135 140
136 141 Coding guidelines
137 142 -----------------
138 143
139 144 We don't have a formal coding/formatting standard. We are currently using a mix
140 145 of Mercurial's (https://www.mercurial-scm.org/wiki/CodingStyle), pep8, and
141 146 consistency with existing code. Run ``scripts/run-all-cleanup`` before
142 147 committing to ensure some basic code formatting consistency.
143 148
144 149 We support both Python 2.6.x and 2.7.x and nothing else. For now we don't care
145 150 about Python 3 compatibility.
146 151
147 152 We try to support the most common modern web browsers. IE9 is still supported
148 153 to the extent it is feasible, IE8 is not.
149 154
150 155 We primarily support Linux and OS X on the server side but Windows should also work.
151 156
152 157 HTML templates should use 2 spaces for indentation ... but be pragmatic. We
153 158 should use templates cleverly and avoid duplication. We should use reasonable
154 159 semantic markup with element classes and IDs that can be used for styling and testing.
155 160 We should only use inline styles in places where it really is semantic (such as
156 161 ``display: none``).
157 162
158 163 JavaScript must use ``;`` between/after statements. Indentation 4 spaces. Inline
159 164 multiline functions should be indented two levels -- one for the ``()`` and one for
160 165 ``{}``.
161 166 Variables holding jQuery objects should be named with a leading ``$``.
162 167
163 168 Commit messages should have a leading short line summarizing the changes. For
164 169 bug fixes, put ``(Issue #123)`` at the end of this line.
165 170
166 171 Use American English grammar and spelling overall. Use `English title case`_ for
167 172 page titles, button labels, headers, and 'labels' for fields in forms.
168 173
169 174 .. _English title case: https://en.wikipedia.org/wiki/Capitalization#Title_case
170 175
171 176 Template helpers (that is, everything in ``kallithea.lib.helpers``)
172 177 should only be referenced from templates. If you need to call a
173 178 helper from the Python code, consider moving the function somewhere
174 179 else (e.g. to the model).
175 180
176 181 Notes on the SQLAlchemy session
177 182 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
178 183
179 184 Each HTTP request runs inside an independent SQLAlchemy session (as well
180 185 as in an independent database transaction). Database model objects
181 186 (almost) always belong to a particular SQLAlchemy session, which means
182 187 that SQLAlchemy will ensure that they're kept in sync with the database
183 188 (but also means that they cannot be shared across requests).
184 189
185 190 Objects can be added to the session using ``Session().add``, but this is
186 191 rarely needed:
187 192
188 193 * When creating a database object by calling the constructor directly,
189 194 it must explicitly be added to the session.
190 195
191 196 * When creating an object using a factory function (like
192 197 ``create_repo``), the returned object has already (by convention)
193 198 been added to the session, and should not be added again.
194 199
195 200 * When getting an object from the session (via ``Session().query`` or
196 201 any of the utility functions that look up objects in the database),
197 202 it's already part of the session, and should not be added again.
198 203 SQLAlchemy monitors attribute modifications automatically for all
199 204 objects it knows about and syncs them to the database.
200 205
201 206 SQLAlchemy also flushes changes to the database automatically; manually
202 207 calling ``Session().flush`` is usually only necessary when the Python
203 208 code needs the database to assign an "auto-increment" primary key ID to
204 209 a freshly created model object (before flushing, the ID attribute will
205 210 be ``None``).
206 211
207 212 TurboGears2 DebugBar
208 213 ^^^^^^^^^^^^^^^^^^^^
209 214
210 215 It is possible to enable the TurboGears2-provided DebugBar_, a toolbar overlayed
211 216 over the Kallithea web interface, allowing you to see:
212 217
213 218 * timing information of the current request, including profiling information
214 219 * request data, including GET data, POST data, cookies, headers and environment
215 220 variables
216 221 * a list of executed database queries, including timing and result values
217 222
218 223 DebugBar is only activated when ``debug = true`` is set in the configuration
219 224 file. This is important, because the DebugBar toolbar will be visible for all
220 225 users, and allow them to see information they should not be allowed to see. Like
221 226 is anyway the case for ``debug = true``, do not use this in production!
222 227
223 228 To enable DebugBar, install ``tgext.debugbar`` and ``kajiki`` (typically via
224 229 ``pip``) and restart Kallithea (in debug mode).
225 230
226 231
227 232 "Roadmap"
228 233 ---------
229 234
230 235 We do not have a road map but are waiting for your contributions. Refer to the
231 236 wiki_ for some ideas of places we might want to go -- contributions in these
232 237 areas are very welcome.
233 238
234 239
235 240 Thank you for your contribution!
236 241 --------------------------------
237 242
238 243
239 244 .. _Weblate: http://weblate.org/
240 245 .. _issue tracking: https://bitbucket.org/conservancy/kallithea/issues?status=new&status=open
241 246 .. _pull requests: https://bitbucket.org/conservancy/kallithea/pull-requests
242 247 .. _bitbucket: http://bitbucket.org/
243 248 .. _mailing list: http://lists.sfconservancy.org/mailman/listinfo/kallithea-general
244 249 .. _kallithea-general: http://lists.sfconservancy.org/mailman/listinfo/kallithea-general
245 250 .. _Hosted Weblate: https://hosted.weblate.org/projects/kallithea/kallithea/
246 251 .. _wiki: https://bitbucket.org/conservancy/kallithea/wiki/Home
247 252 .. _DebugBar: https://github.com/TurboGears/tgext.debugbar
@@ -1,568 +1,571 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 """
15 15 kallithea.tests.other.manual_test_vcs_operations
16 16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 17
18 18 Test suite for making push/pull operations.
19 19
20 20 Run it in two terminals::
21 21 gearbox serve -c kallithea/tests/test.ini
22 22 KALLITHEA_WHOOSH_TEST_DISABLE=1 KALLITHEA_NO_TMP_PATH=1 py.test kallithea/tests/other/manual_test_vcs_operations.py
23 23
24 24 You must have git > 1.8.1 for tests to work fine
25 25
26 26 This file was forked by the Kallithea project in July 2014.
27 27 Original author and date, and relevant copyright and licensing information is below:
28 28 :created_on: Dec 30, 2010
29 29 :author: marcink
30 30 :copyright: (c) 2013 RhodeCode GmbH, and others.
31 31 :license: GPLv3, see LICENSE.md for more details.
32 32
33 33 """
34 34
35 35 import os
36 36 import re
37 37 import tempfile
38 38 import time
39 39 import pytest
40 40
41 41 from tempfile import _RandomNameSequence
42 42 from subprocess import Popen, PIPE
43 43
44 44 from kallithea.tests.base import *
45 45 from kallithea.tests.fixture import Fixture
46 46 from kallithea.model.db import User, Repository, UserIpMap, CacheInvalidation
47 47 from kallithea.model.meta import Session
48 48 from kallithea.model.repo import RepoModel
49 49 from kallithea.model.user import UserModel
50 50
51 51 DEBUG = True
52 52 HOST = '127.0.0.1:4999' # test host
53 53
54 54 fixture = Fixture()
55 55
56 56
57 57 class Command(object):
58 58
59 59 def __init__(self, cwd):
60 60 self.cwd = cwd
61 61
62 62 def execute(self, cmd, *args, **environ):
63 63 """
64 64 Runs command on the system with given ``args``.
65 65 """
66 66
67 67 command = cmd + ' ' + ' '.join(args)
68 68 ignoreReturnCode = environ.pop('ignoreReturnCode', False)
69 69 if DEBUG:
70 70 print '*** CMD %s ***' % command
71 71 testenv = dict(os.environ)
72 72 testenv['LANG'] = 'en_US.UTF-8'
73 73 testenv['LANGUAGE'] = 'en_US:en'
74 74 testenv.update(environ)
75 75 p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, cwd=self.cwd, env=testenv)
76 76 stdout, stderr = p.communicate()
77 77 if DEBUG:
78 78 if stdout:
79 79 print 'stdout:', repr(stdout)
80 80 if stderr:
81 81 print 'stderr:', repr(stderr)
82 82 if not ignoreReturnCode:
83 83 assert p.returncode == 0
84 84 return stdout, stderr
85 85
86 86
87 87 def _get_tmp_dir():
88 88 return tempfile.mkdtemp(prefix='rc_integration_test')
89 89
90 90
91 91 def _construct_url(repo, **kwargs):
92 92 """Return a clone url for the provided repo path.
93 93 Optional named parameters: user, passwd and host."""
94 94 params = {
95 95 'user': TEST_USER_ADMIN_LOGIN,
96 96 'passwd': TEST_USER_ADMIN_PASS,
97 97 'host': HOST,
98 98 'cloned_repo': repo,
99 99 }
100 100 params.update(**kwargs)
101 101 if params['user'] and params['passwd']:
102 102 _url = 'http://%(user)s:%(passwd)s@%(host)s/%(cloned_repo)s' % params
103 103 else:
104 104 _url = 'http://(host)s/%(cloned_repo)s' % params
105 105 return _url
106 106
107 107
108 108 def _add_files_and_push(vcs, DEST, ignoreReturnCode=False, **kwargs):
109 109 """
110 110 Generate some files, add it to DEST repo and push back
111 111 vcs is git or hg and defines what VCS we want to make those files for
112 112
113 113 :param vcs:
114 114 :param DEST:
115 115 """
116 116 # commit some stuff into this repo
117 117 cwd = os.path.join(DEST)
118 118 #added_file = '%ssetupΔ…ΕΌΕΊΔ‡.py' % _RandomNameSequence().next()
119 119 added_file = '%ssetup.py' % _RandomNameSequence().next()
120 120 Command(cwd).execute('touch %s' % added_file)
121 121 Command(cwd).execute('%s add %s' % (vcs, added_file))
122 122
123 123 email = 'me@example.com'
124 124 if os.name == 'nt':
125 125 author_str = 'User <%s>' % email
126 126 else:
127 127 author_str = 'User ǝɯɐᴎ <%s>' % email
128 128 for i in xrange(kwargs.get('files_no', 3)):
129 129 cmd = """echo "added_line%s" >> %s""" % (i, added_file)
130 130 Command(cwd).execute(cmd)
131 131 if vcs == 'hg':
132 132 cmd = """hg commit -m "committed new %s" -u "%s" "%s" """ % (
133 133 i, author_str, added_file
134 134 )
135 135 elif vcs == 'git':
136 136 cmd = """git commit -m "committed new %s" --author "%s" "%s" """ % (
137 137 i, author_str, added_file
138 138 )
139 139 # git commit needs EMAIL on some machines
140 140 Command(cwd).execute(cmd, EMAIL=email)
141 141
142 142 # PUSH it back
143 143 _REPO = None
144 144 if vcs == 'hg':
145 145 _REPO = HG_REPO
146 146 elif vcs == 'git':
147 147 _REPO = GIT_REPO
148 148
149 149 clone_url = _construct_url(_REPO, **kwargs)
150 150 if 'clone_url' in kwargs:
151 151 clone_url = kwargs['clone_url']
152 152 stdout = stderr = None
153 153 if vcs == 'hg':
154 154 stdout, stderr = Command(cwd).execute('hg push --verbose', clone_url, ignoreReturnCode=ignoreReturnCode)
155 155 elif vcs == 'git':
156 156 stdout, stderr = Command(cwd).execute('git push --verbose', clone_url, "master", ignoreReturnCode=ignoreReturnCode)
157 157
158 158 return stdout, stderr
159 159
160 160
161 161 def set_anonymous_access(enable=True):
162 162 user = User.get_default_user()
163 163 user.active = enable
164 164 Session().commit()
165 165 print '\tanonymous access is now:', enable
166 166 if enable != User.get_default_user().active:
167 167 raise Exception('Cannot set anonymous access')
168 168
169 169
170 170 #==============================================================================
171 171 # TESTS
172 172 #==============================================================================
173 173
174 174
175 175 def _check_proper_git_push(stdout, stderr):
176 176 #WTF Git stderr is output ?!
177 177 assert 'fatal' not in stderr
178 178 assert 'rejected' not in stderr
179 179 assert 'Pushing to' in stderr
180 180 assert 'master -> master' in stderr
181 181
182 182
183 183 @pytest.mark.usefixtures("test_context_fixture")
184 184 class TestVCSOperations(TestController):
185 185
186 186 @classmethod
187 187 def setup_class(cls):
188 188 #DISABLE ANONYMOUS ACCESS
189 189 set_anonymous_access(False)
190 190
191 191 def setup_method(self, method):
192 192 r = Repository.get_by_repo_name(GIT_REPO)
193 193 Repository.unlock(r)
194 194 r.enable_locking = False
195 195 Session().commit()
196 196
197 197 r = Repository.get_by_repo_name(HG_REPO)
198 198 Repository.unlock(r)
199 199 r.enable_locking = False
200 200 Session().commit()
201 201
202 202 def test_clone_hg_repo_by_admin(self):
203 203 clone_url = _construct_url(HG_REPO)
204 204 stdout, stderr = Command(tempfile.gettempdir()).execute('hg clone', clone_url, _get_tmp_dir())
205 205
206 206 assert 'requesting all changes' in stdout
207 207 assert 'adding changesets' in stdout
208 208 assert 'adding manifests' in stdout
209 209 assert 'adding file changes' in stdout
210 210
211 211 assert stderr == ''
212 212
213 213 def test_clone_git_repo_by_admin(self):
214 214 clone_url = _construct_url(GIT_REPO)
215 215 stdout, stderr = Command(tempfile.gettempdir()).execute('git clone', clone_url, _get_tmp_dir())
216 216
217 217 assert 'Cloning into' in stdout + stderr
218 218 assert stderr == '' or stdout == ''
219 219
220 220 def test_clone_wrong_credentials_hg(self):
221 221 clone_url = _construct_url(HG_REPO, passwd='bad!')
222 222 stdout, stderr = Command(tempfile.gettempdir()).execute('hg clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
223 223 assert 'abort: authorization failed' in stderr
224 224
225 225 def test_clone_wrong_credentials_git(self):
226 226 clone_url = _construct_url(GIT_REPO, passwd='bad!')
227 227 stdout, stderr = Command(tempfile.gettempdir()).execute('git clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
228 228 assert 'fatal: Authentication failed' in stderr
229 229
230 230 def test_clone_git_dir_as_hg(self):
231 231 clone_url = _construct_url(GIT_REPO)
232 232 stdout, stderr = Command(tempfile.gettempdir()).execute('hg clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
233 233 assert 'HTTP Error 404: Not Found' in stderr
234 234
235 235 def test_clone_hg_repo_as_git(self):
236 236 clone_url = _construct_url(HG_REPO)
237 237 stdout, stderr = Command(tempfile.gettempdir()).execute('git clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
238 238 assert 'not found' in stderr
239 239
240 240 def test_clone_non_existing_path_hg(self):
241 241 clone_url = _construct_url('trololo')
242 242 stdout, stderr = Command(tempfile.gettempdir()).execute('hg clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
243 243 assert 'HTTP Error 404: Not Found' in stderr
244 244
245 245 def test_clone_non_existing_path_git(self):
246 246 clone_url = _construct_url('trololo')
247 247 stdout, stderr = Command(tempfile.gettempdir()).execute('git clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
248 248 assert 'not found' in stderr
249 249
250 250 def test_push_new_file_hg(self):
251 251 DEST = _get_tmp_dir()
252 252 clone_url = _construct_url(HG_REPO)
253 253 stdout, stderr = Command(tempfile.gettempdir()).execute('hg clone', clone_url, DEST)
254 254
255 255 fork_name = '%s_fork%s' % (HG_REPO, _RandomNameSequence().next())
256 256 fixture.create_fork(HG_REPO, fork_name)
257 257 clone_url = _construct_url(fork_name).split()[0]
258 258 stdout, stderr = _add_files_and_push('hg', DEST, clone_url=clone_url)
259 259
260 260 assert 'pushing to' in stdout
261 261 assert 'Repository size' in stdout
262 262 assert 'Last revision is now' in stdout
263 263
264 264 def test_push_new_file_git(self):
265 265 DEST = _get_tmp_dir()
266 266 clone_url = _construct_url(GIT_REPO)
267 267 stdout, stderr = Command(tempfile.gettempdir()).execute('git clone', clone_url, DEST)
268 268
269 269 # commit some stuff into this repo
270 270 fork_name = '%s_fork%s' % (GIT_REPO, _RandomNameSequence().next())
271 271 fixture.create_fork(GIT_REPO, fork_name)
272 272 clone_url = _construct_url(fork_name).split()[0]
273 273 stdout, stderr = _add_files_and_push('git', DEST, clone_url=clone_url)
274 274 print [(x.repo_full_path,x.repo_path) for x in Repository.query()] # TODO: what is this for
275 275 _check_proper_git_push(stdout, stderr)
276 276
277 277 def test_push_invalidates_cache_hg(self):
278 278 key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
279 279 ==HG_REPO).scalar()
280 280 if not key:
281 281 key = CacheInvalidation(HG_REPO, HG_REPO)
282 282 Session().add(key)
283 283
284 284 key.cache_active = True
285 285 Session().commit()
286 286
287 287 DEST = _get_tmp_dir()
288 288 clone_url = _construct_url(HG_REPO)
289 289 stdout, stderr = Command(tempfile.gettempdir()).execute('hg clone', clone_url, DEST)
290 290
291 291 fork_name = '%s_fork%s' % (HG_REPO, _RandomNameSequence().next())
292 292 fixture.create_fork(HG_REPO, fork_name)
293 293 clone_url = _construct_url(fork_name).split()[0]
294 294 stdout, stderr = _add_files_and_push('hg', DEST, files_no=1, clone_url=clone_url)
295 295
296 296 key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
297 297 ==fork_name).all()
298 298 assert key == []
299 299
300 300 def test_push_invalidates_cache_git(self):
301 301 key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
302 302 ==GIT_REPO).scalar()
303 303 if not key:
304 304 key = CacheInvalidation(GIT_REPO, GIT_REPO)
305 305 Session().add(key)
306 306
307 307 key.cache_active = True
308 308 Session().commit()
309 309
310 310 DEST = _get_tmp_dir()
311 311 clone_url = _construct_url(GIT_REPO)
312 312 stdout, stderr = Command(tempfile.gettempdir()).execute('git clone', clone_url, DEST)
313 313
314 314 # commit some stuff into this repo
315 315 fork_name = '%s_fork%s' % (GIT_REPO, _RandomNameSequence().next())
316 316 fixture.create_fork(GIT_REPO, fork_name)
317 317 clone_url = _construct_url(fork_name).split()[0]
318 318 stdout, stderr = _add_files_and_push('git', DEST, files_no=1, clone_url=clone_url)
319 319 _check_proper_git_push(stdout, stderr)
320 320
321 321 key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
322 322 ==fork_name).all()
323 323 assert key == []
324 324
325 325 def test_push_wrong_credentials_hg(self):
326 326 DEST = _get_tmp_dir()
327 327 clone_url = _construct_url(HG_REPO)
328 328 stdout, stderr = Command(tempfile.gettempdir()).execute('hg clone', clone_url, DEST)
329 329
330 330 stdout, stderr = _add_files_and_push('hg', DEST, user='bad',
331 331 passwd='name', ignoreReturnCode=True)
332 332
333 333 assert 'abort: authorization failed' in stderr
334 334
335 335 def test_push_wrong_credentials_git(self):
336 336 DEST = _get_tmp_dir()
337 337 clone_url = _construct_url(GIT_REPO)
338 338 stdout, stderr = Command(tempfile.gettempdir()).execute('git clone', clone_url, DEST)
339 339
340 340 stdout, stderr = _add_files_and_push('git', DEST, user='bad',
341 341 passwd='name', ignoreReturnCode=True)
342 342
343 343 assert 'fatal: Authentication failed' in stderr
344 344
345 345 def test_push_back_to_wrong_url_hg(self):
346 346 DEST = _get_tmp_dir()
347 347 clone_url = _construct_url(HG_REPO)
348 348 stdout, stderr = Command(tempfile.gettempdir()).execute('hg clone', clone_url, DEST)
349 349
350 350 stdout, stderr = _add_files_and_push('hg', DEST,
351 351 clone_url='http://%s/tmp' % HOST,
352 352 ignoreReturnCode = True)
353 353
354 354 assert 'HTTP Error 404: Not Found' in stderr
355 355
356 356 def test_push_back_to_wrong_url_git(self):
357 357 DEST = _get_tmp_dir()
358 358 clone_url = _construct_url(GIT_REPO)
359 359 stdout, stderr = Command(tempfile.gettempdir()).execute('git clone', clone_url, DEST)
360 360
361 361 stdout, stderr = _add_files_and_push('git', DEST,
362 362 clone_url='http://%s/tmp' % HOST,
363 363 ignoreReturnCode = True)
364 364
365 365 assert 'not found' in stderr
366 366
367 367 def test_clone_and_create_lock_hg(self):
368 368 # enable locking
369 369 r = Repository.get_by_repo_name(HG_REPO)
370 370 r.enable_locking = True
371 371 Session().commit()
372 372 # clone
373 373 clone_url = _construct_url(HG_REPO)
374 374 stdout, stderr = Command(tempfile.gettempdir()).execute('hg clone', clone_url, _get_tmp_dir())
375 375
376 376 #check if lock was made
377 377 r = Repository.get_by_repo_name(HG_REPO)
378 378 assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
379 379
380 380 def test_clone_and_create_lock_git(self):
381 381 # enable locking
382 382 r = Repository.get_by_repo_name(GIT_REPO)
383 383 r.enable_locking = True
384 384 Session().commit()
385 385 # clone
386 386 clone_url = _construct_url(GIT_REPO)
387 387 stdout, stderr = Command(tempfile.gettempdir()).execute('git clone', clone_url, _get_tmp_dir())
388 388
389 389 #check if lock was made
390 390 r = Repository.get_by_repo_name(GIT_REPO)
391 391 assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
392 392
393 393 def test_clone_after_repo_was_locked_hg(self):
394 394 #lock repo
395 395 r = Repository.get_by_repo_name(HG_REPO)
396 396 Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
397 397 #pull fails since repo is locked
398 398 clone_url = _construct_url(HG_REPO)
399 399 stdout, stderr = Command(tempfile.gettempdir()).execute('hg clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
400 400 msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`"""
401 401 % (HG_REPO, TEST_USER_ADMIN_LOGIN))
402 402 assert msg in stderr
403 403
404 404 def test_clone_after_repo_was_locked_git(self):
405 405 #lock repo
406 406 r = Repository.get_by_repo_name(GIT_REPO)
407 407 Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
408 408 #pull fails since repo is locked
409 409 clone_url = _construct_url(GIT_REPO)
410 410 stdout, stderr = Command(tempfile.gettempdir()).execute('git clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
411 411 msg = ("""The requested URL returned error: 423""")
412 412 assert msg in stderr
413 413
414 414 def test_push_on_locked_repo_by_other_user_hg(self):
415 415 #clone some temp
416 416 DEST = _get_tmp_dir()
417 417 clone_url = _construct_url(HG_REPO)
418 418 stdout, stderr = Command(tempfile.gettempdir()).execute('hg clone', clone_url, DEST)
419 419
420 420 #lock repo
421 421 r = Repository.get_by_repo_name(HG_REPO)
422 422 # let this user actually push !
423 423 RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN,
424 424 perm='repository.write')
425 425 Session().commit()
426 426 Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
427 427
428 428 #push fails repo is locked by other user !
429 429 stdout, stderr = _add_files_and_push('hg', DEST,
430 430 user=TEST_USER_REGULAR_LOGIN,
431 431 passwd=TEST_USER_REGULAR_PASS,
432 432 ignoreReturnCode=True)
433 433 msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`"""
434 434 % (HG_REPO, TEST_USER_ADMIN_LOGIN))
435 435 assert msg in stderr
436 436
437 437 def test_push_on_locked_repo_by_other_user_git(self):
438 # Note: Git hooks must be executable on unix. This test will thus fail
439 # for example on Linux if /tmp is mounted noexec.
440
438 441 #clone some temp
439 442 DEST = _get_tmp_dir()
440 443 clone_url = _construct_url(GIT_REPO)
441 444 stdout, stderr = Command(tempfile.gettempdir()).execute('git clone', clone_url, DEST)
442 445
443 446 #lock repo
444 447 r = Repository.get_by_repo_name(GIT_REPO)
445 448 # let this user actually push !
446 449 RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN,
447 450 perm='repository.write')
448 451 Session().commit()
449 452 Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id)
450 453
451 454 #push fails repo is locked by other user !
452 455 stdout, stderr = _add_files_and_push('git', DEST,
453 456 user=TEST_USER_REGULAR_LOGIN,
454 457 passwd=TEST_USER_REGULAR_PASS,
455 458 ignoreReturnCode=True)
456 459 err = 'Repository `%s` locked by user `%s`' % (GIT_REPO, TEST_USER_ADMIN_LOGIN)
457 460 assert err in stderr
458 461
459 462 #TODO: fix this somehow later on Git, Git is stupid and even if we throw
460 463 #back 423 to it, it makes ANOTHER request and we fail there with 405 :/
461 464
462 465 msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`"""
463 466 % (GIT_REPO, TEST_USER_ADMIN_LOGIN))
464 467 #msg = "405 Method Not Allowed"
465 468 #assert msg in stderr
466 469
467 470 def test_push_unlocks_repository_hg(self):
468 471 # enable locking
469 472 fork_name = '%s_fork%s' % (HG_REPO, _RandomNameSequence().next())
470 473 fixture.create_fork(HG_REPO, fork_name)
471 474 r = Repository.get_by_repo_name(fork_name)
472 475 r.enable_locking = True
473 476 Session().commit()
474 477 #clone some temp
475 478 DEST = _get_tmp_dir()
476 479 clone_url = _construct_url(fork_name)
477 480 stdout, stderr = Command(tempfile.gettempdir()).execute('hg clone', clone_url, DEST)
478 481
479 482 #check for lock repo after clone
480 483 r = Repository.get_by_repo_name(fork_name)
481 484 uid = User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
482 485 assert r.locked[0] == uid
483 486
484 487 #push is ok and repo is now unlocked
485 488 stdout, stderr = _add_files_and_push('hg', DEST, clone_url=clone_url.split()[0])
486 489 assert str('remote: Released lock on repo `%s`' % fork_name) in stdout
487 490 #we need to cleanup the Session Here !
488 491 Session.remove()
489 492 r = Repository.get_by_repo_name(fork_name)
490 493 assert r.locked == [None, None]
491 494
492 495 #TODO: fix me ! somehow during tests hooks don't get called on Git
493 496 def test_push_unlocks_repository_git(self):
494 497 # enable locking
495 498 fork_name = '%s_fork%s' % (GIT_REPO, _RandomNameSequence().next())
496 499 fixture.create_fork(GIT_REPO, fork_name)
497 500 r = Repository.get_by_repo_name(fork_name)
498 501 r.enable_locking = True
499 502 Session().commit()
500 503 #clone some temp
501 504 DEST = _get_tmp_dir()
502 505 clone_url = _construct_url(fork_name)
503 506 stdout, stderr = Command(tempfile.gettempdir()).execute('git clone', clone_url, DEST)
504 507
505 508 #check for lock repo after clone
506 509 r = Repository.get_by_repo_name(fork_name)
507 510 assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id
508 511
509 512 #push is ok and repo is now unlocked
510 513 stdout, stderr = _add_files_and_push('git', DEST, clone_url=clone_url.split()[0])
511 514 _check_proper_git_push(stdout, stderr)
512 515
513 516 assert ('remote: Released lock on repo `%s`' % fork_name) in stderr
514 517 #we need to cleanup the Session Here !
515 518 Session.remove()
516 519 r = Repository.get_by_repo_name(fork_name)
517 520 assert r.locked == [None, None]
518 521
519 522 def test_ip_restriction_hg(self):
520 523 user_model = UserModel()
521 524 try:
522 525 user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
523 526 Session().commit()
524 527 clone_url = _construct_url(HG_REPO)
525 528 stdout, stderr = Command(tempfile.gettempdir()).execute('hg clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
526 529 assert 'abort: HTTP Error 403: Forbidden' in stderr
527 530 finally:
528 531 #release IP restrictions
529 532 for ip in UserIpMap.query():
530 533 UserIpMap.delete(ip.ip_id)
531 534 Session().commit()
532 535
533 536 # IP permissions are cached, need to wait for the cache in the server process to expire
534 537 time.sleep(1.5)
535 538
536 539 clone_url = _construct_url(HG_REPO)
537 540 stdout, stderr = Command(tempfile.gettempdir()).execute('hg clone', clone_url, _get_tmp_dir())
538 541
539 542 assert 'requesting all changes' in stdout
540 543 assert 'adding changesets' in stdout
541 544 assert 'adding manifests' in stdout
542 545 assert 'adding file changes' in stdout
543 546
544 547 assert stderr == ''
545 548
546 549 def test_ip_restriction_git(self):
547 550 user_model = UserModel()
548 551 try:
549 552 user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
550 553 Session().commit()
551 554 clone_url = _construct_url(GIT_REPO)
552 555 stdout, stderr = Command(tempfile.gettempdir()).execute('git clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
553 556 # The message apparently changed in Git 1.8.3, so match it loosely.
554 557 assert re.search(r'\b403\b', stderr)
555 558 finally:
556 559 #release IP restrictions
557 560 for ip in UserIpMap.query():
558 561 UserIpMap.delete(ip.ip_id)
559 562 Session().commit()
560 563
561 564 # IP permissions are cached, need to wait for the cache in the server process to expire
562 565 time.sleep(1.5)
563 566
564 567 clone_url = _construct_url(GIT_REPO)
565 568 stdout, stderr = Command(tempfile.gettempdir()).execute('git clone', clone_url, _get_tmp_dir())
566 569
567 570 assert 'Cloning into' in stdout + stderr
568 571 assert stderr == '' or stdout == ''
General Comments 0
You need to be logged in to leave comments. Login now