Show More
@@ -0,0 +1,295 b'' | |||
|
1 | #!/usr/bin/env python3 | |
|
2 | ||
|
3 | ||
|
4 | import re | |
|
5 | import sys | |
|
6 | ||
|
7 | ||
|
8 | ignored_modules = set(''' | |
|
9 | argparse | |
|
10 | base64 | |
|
11 | bcrypt | |
|
12 | binascii | |
|
13 | bleach | |
|
14 | calendar | |
|
15 | celery | |
|
16 | celery | |
|
17 | chardet | |
|
18 | click | |
|
19 | collections | |
|
20 | configparser | |
|
21 | copy | |
|
22 | csv | |
|
23 | ctypes | |
|
24 | datetime | |
|
25 | dateutil | |
|
26 | decimal | |
|
27 | decorator | |
|
28 | difflib | |
|
29 | distutils | |
|
30 | docutils | |
|
31 | ||
|
32 | errno | |
|
33 | fileinput | |
|
34 | functools | |
|
35 | getpass | |
|
36 | grp | |
|
37 | hashlib | |
|
38 | hmac | |
|
39 | html | |
|
40 | http | |
|
41 | imp | |
|
42 | importlib | |
|
43 | inspect | |
|
44 | io | |
|
45 | ipaddr | |
|
46 | IPython | |
|
47 | isapi_wsgi | |
|
48 | itertools | |
|
49 | json | |
|
50 | kajiki | |
|
51 | ldap | |
|
52 | logging | |
|
53 | mako | |
|
54 | markdown | |
|
55 | mimetypes | |
|
56 | mock | |
|
57 | msvcrt | |
|
58 | multiprocessing | |
|
59 | operator | |
|
60 | os | |
|
61 | paginate | |
|
62 | paginate_sqlalchemy | |
|
63 | pam | |
|
64 | paste | |
|
65 | pkg_resources | |
|
66 | platform | |
|
67 | posixpath | |
|
68 | pprint | |
|
69 | pwd | |
|
70 | pyflakes | |
|
71 | pytest | |
|
72 | pytest_localserver | |
|
73 | random | |
|
74 | re | |
|
75 | routes | |
|
76 | setuptools | |
|
77 | shlex | |
|
78 | shutil | |
|
79 | smtplib | |
|
80 | socket | |
|
81 | ssl | |
|
82 | stat | |
|
83 | string | |
|
84 | struct | |
|
85 | subprocess | |
|
86 | sys | |
|
87 | tarfile | |
|
88 | tempfile | |
|
89 | textwrap | |
|
90 | tgext | |
|
91 | threading | |
|
92 | time | |
|
93 | traceback | |
|
94 | traitlets | |
|
95 | types | |
|
96 | urllib | |
|
97 | urlobject | |
|
98 | uuid | |
|
99 | warnings | |
|
100 | webhelpers2 | |
|
101 | webob | |
|
102 | webtest | |
|
103 | whoosh | |
|
104 | win32traceutil | |
|
105 | zipfile | |
|
106 | '''.split()) | |
|
107 | ||
|
108 | top_modules = set(''' | |
|
109 | kallithea.alembic | |
|
110 | kallithea.bin | |
|
111 | kallithea.config | |
|
112 | kallithea.controllers | |
|
113 | kallithea.templates.py | |
|
114 | scripts | |
|
115 | '''.split()) | |
|
116 | ||
|
117 | bottom_external_modules = set(''' | |
|
118 | tg | |
|
119 | mercurial | |
|
120 | sqlalchemy | |
|
121 | alembic | |
|
122 | formencode | |
|
123 | pygments | |
|
124 | dulwich | |
|
125 | beaker | |
|
126 | psycopg2 | |
|
127 | docs | |
|
128 | setup | |
|
129 | conftest | |
|
130 | '''.split()) | |
|
131 | ||
|
132 | normal_modules = set(''' | |
|
133 | kallithea | |
|
134 | kallithea.lib.celerylib.tasks | |
|
135 | kallithea.lib | |
|
136 | kallithea.lib.auth | |
|
137 | kallithea.lib.auth_modules | |
|
138 | kallithea.lib.base | |
|
139 | kallithea.lib.celerylib | |
|
140 | kallithea.lib.db_manage | |
|
141 | kallithea.lib.helpers | |
|
142 | kallithea.lib.hooks | |
|
143 | kallithea.lib.indexers | |
|
144 | kallithea.lib.utils | |
|
145 | kallithea.lib.utils2 | |
|
146 | kallithea.lib.vcs | |
|
147 | kallithea.lib.webutils | |
|
148 | kallithea.model | |
|
149 | kallithea.model.scm | |
|
150 | kallithea.templates.py | |
|
151 | '''.split()) | |
|
152 | ||
|
153 | shown_modules = normal_modules | top_modules | |
|
154 | ||
|
155 | # break the chains somehow - this is a cleanup TODO list | |
|
156 | known_violations = [ | |
|
157 | ('kallithea.lib.auth_modules', 'kallithea.lib.auth'), # needs base&facade | |
|
158 | ('kallithea.lib.utils', 'kallithea.model'), # clean up utils | |
|
159 | ('kallithea.lib.utils', 'kallithea.model.db'), | |
|
160 | ('kallithea.lib.utils', 'kallithea.model.scm'), | |
|
161 | ('kallithea.lib.celerylib.tasks', 'kallithea.lib.helpers'), | |
|
162 | ('kallithea.lib.celerylib.tasks', 'kallithea.lib.hooks'), | |
|
163 | ('kallithea.lib.celerylib.tasks', 'kallithea.lib.indexers'), | |
|
164 | ('kallithea.lib.celerylib.tasks', 'kallithea.model'), | |
|
165 | ('kallithea.model', 'kallithea.lib.auth'), # auth.HasXXX | |
|
166 | ('kallithea.model', 'kallithea.lib.auth_modules'), # validators | |
|
167 | ('kallithea.model', 'kallithea.lib.helpers'), | |
|
168 | ('kallithea.model', 'kallithea.lib.hooks'), # clean up hooks | |
|
169 | ('kallithea.model', 'kallithea.model.scm'), | |
|
170 | ('kallithea.model.scm', 'kallithea.lib.hooks'), | |
|
171 | ] | |
|
172 | ||
|
173 | extra_edges = [ | |
|
174 | ('kallithea.config', 'kallithea.controllers'), # through TG | |
|
175 | ('kallithea.lib.auth', 'kallithea.lib.auth_modules'), # custom loader | |
|
176 | ] | |
|
177 | ||
|
178 | ||
|
179 | def normalize(s): | |
|
180 | """Given a string with dot path, return the string it should be shown as.""" | |
|
181 | parts = s.replace('.__init__', '').split('.') | |
|
182 | short_2 = '.'.join(parts[:2]) | |
|
183 | short_3 = '.'.join(parts[:3]) | |
|
184 | short_4 = '.'.join(parts[:4]) | |
|
185 | if parts[0] in ['scripts', 'contributor_data', 'i18n_utils']: | |
|
186 | return 'scripts' | |
|
187 | if short_3 == 'kallithea.model.meta': | |
|
188 | return 'kallithea.model.db' | |
|
189 | if parts[:4] == ['kallithea', 'lib', 'vcs', 'ssh']: | |
|
190 | return 'kallithea.lib.vcs.ssh' | |
|
191 | if short_4 in shown_modules: | |
|
192 | return short_4 | |
|
193 | if short_3 in shown_modules: | |
|
194 | return short_3 | |
|
195 | if short_2 in shown_modules: | |
|
196 | return short_2 | |
|
197 | if short_2 == 'kallithea.tests': | |
|
198 | return None | |
|
199 | if parts[0] in ignored_modules: | |
|
200 | return None | |
|
201 | assert parts[0] in bottom_external_modules, parts | |
|
202 | return parts[0] | |
|
203 | ||
|
204 | ||
|
205 | def main(filenames): | |
|
206 | if not filenames or filenames[0].startswith('-'): | |
|
207 | print('''\ | |
|
208 | Usage: | |
|
209 | hg files 'set:!binary()&grep("^#!.*python")' 'set:**.py' | xargs scripts/deps.py | |
|
210 | dot -Tsvg deps.dot > deps.svg | |
|
211 | ''') | |
|
212 | raise SystemExit(1) | |
|
213 | ||
|
214 | files_imports = dict() # map filenames to its imports | |
|
215 | import_deps = set() # set of tuples with module name and its imports | |
|
216 | for fn in filenames: | |
|
217 | with open(fn) as f: | |
|
218 | s = f.read() | |
|
219 | ||
|
220 | dot_name = (fn[:-3] if fn.endswith('.py') else fn).replace('/', '.') | |
|
221 | file_imports = set() | |
|
222 | for m in re.finditer(r'^ *(?:from ([^ ]*) import (?:([a-zA-Z].*)|\(([^)]*)\))|import (.*))$', s, re.MULTILINE): | |
|
223 | m_from, m_from_import, m_from_import2, m_import = m.groups() | |
|
224 | if m_from: | |
|
225 | pre = m_from + '.' | |
|
226 | if pre.startswith('.'): | |
|
227 | pre = dot_name.rsplit('.', 1)[0] + pre | |
|
228 | importlist = m_from_import or m_from_import2 | |
|
229 | else: | |
|
230 | pre = '' | |
|
231 | importlist = m_import | |
|
232 | for imp in importlist.split('#', 1)[0].split(','): | |
|
233 | full_imp = pre + imp.strip().split(' as ', 1)[0] | |
|
234 | file_imports.add(full_imp) | |
|
235 | import_deps.add((dot_name, full_imp)) | |
|
236 | files_imports[fn] = file_imports | |
|
237 | ||
|
238 | # dump out all deps for debugging and analysis | |
|
239 | with open('deps.txt', 'w') as f: | |
|
240 | for fn, file_imports in sorted(files_imports.items()): | |
|
241 | for file_import in sorted(file_imports): | |
|
242 | if file_import.split('.', 1)[0] in ignored_modules: | |
|
243 | continue | |
|
244 | f.write('%s: %s\n' % (fn, file_import)) | |
|
245 | ||
|
246 | # find leafs that haven't been ignored - they are the important external dependencies and shown in the bottom row | |
|
247 | only_imported = set( | |
|
248 | set(normalize(b) for a, b in import_deps) - | |
|
249 | set(normalize(a) for a, b in import_deps) - | |
|
250 | set([None, 'kallithea']) | |
|
251 | ) | |
|
252 | ||
|
253 | normalized_dep_edges = set() | |
|
254 | for dot_name, full_imp in import_deps: | |
|
255 | a = normalize(dot_name) | |
|
256 | b = normalize(full_imp) | |
|
257 | if a is None or b is None or a == b: | |
|
258 | continue | |
|
259 | normalized_dep_edges.add((a, b)) | |
|
260 | #print((dot_name, full_imp, a, b)) | |
|
261 | normalized_dep_edges.update(extra_edges) | |
|
262 | ||
|
263 | unseen_shown_modules = shown_modules.difference(a for a, b in normalized_dep_edges).difference(b for a, b in normalized_dep_edges) | |
|
264 | assert not unseen_shown_modules, unseen_shown_modules | |
|
265 | ||
|
266 | with open('deps.dot', 'w') as f: | |
|
267 | f.write('digraph {\n') | |
|
268 | f.write('subgraph { rank = same; %s}\n' % ''.join('"%s"; ' % s for s in sorted(top_modules))) | |
|
269 | f.write('subgraph { rank = same; %s}\n' % ''.join('"%s"; ' % s for s in sorted(only_imported))) | |
|
270 | for a, b in sorted(normalized_dep_edges): | |
|
271 | f.write(' "%s" -> "%s"%s\n' % (a, b, ' [color=red]' if (a, b) in known_violations else ' [color=green]' if (a, b) in extra_edges else '')) | |
|
272 | f.write('}\n') | |
|
273 | ||
|
274 | # verify dependencies by untangling dependency chain bottom-up: | |
|
275 | todo = set(normalized_dep_edges) | |
|
276 | for x in known_violations: | |
|
277 | todo.remove(x) | |
|
278 | ||
|
279 | while todo: | |
|
280 | depending = set(a for a, b in todo) | |
|
281 | depended = set(b for a, b in todo) | |
|
282 | drop = depended - depending | |
|
283 | if not drop: | |
|
284 | print('ERROR: cycles:', len(todo)) | |
|
285 | for x in sorted(todo): | |
|
286 | print('%s,' % (x,)) | |
|
287 | raise SystemExit(1) | |
|
288 | #for do_b in sorted(drop): | |
|
289 | # print('Picking', do_b, '- unblocks:', ' '.join(a for a, b in sorted((todo)) if b == do_b)) | |
|
290 | todo = set((a, b) for a, b in todo if b in depending) | |
|
291 | #print() | |
|
292 | ||
|
293 | ||
|
294 | if __name__ == '__main__': | |
|
295 | main(sys.argv[1:]) |
@@ -1,55 +1,58 b'' | |||
|
1 | 1 | syntax: glob |
|
2 | 2 | *.pyc |
|
3 | 3 | *.swp |
|
4 | 4 | *.sqlite |
|
5 | 5 | *.tox |
|
6 | 6 | *.egg-info |
|
7 | 7 | *.egg |
|
8 | 8 | *.mo |
|
9 | 9 | *.orig |
|
10 | 10 | *.rej |
|
11 | 11 | *.bak |
|
12 | 12 | .eggs/ |
|
13 | 13 | |
|
14 | 14 | syntax: regexp |
|
15 | 15 | ^extensions\.py$ |
|
16 | 16 | ^build$ |
|
17 | 17 | ^dist$ |
|
18 | 18 | ^docs/build$ |
|
19 | 19 | ^docs/_build$ |
|
20 | 20 | ^data$ |
|
21 | 21 | ^sql_dumps$ |
|
22 | 22 | ^\.settings$ |
|
23 | 23 | ^\.project$ |
|
24 | 24 | ^\.pydevproject$ |
|
25 | 25 | ^\.coverage$ |
|
26 | 26 | ^kallithea/front-end/node_modules$ |
|
27 | 27 | ^kallithea/front-end/package-lock\.json$ |
|
28 | 28 | ^kallithea/front-end/theme\.less$ |
|
29 | 29 | ^kallithea/front-end/tmp$ |
|
30 | 30 | ^kallithea/public/codemirror$ |
|
31 | 31 | ^kallithea/public/css/select2-spinner\.gif$ |
|
32 | 32 | ^kallithea/public/css/select2\.png$ |
|
33 | 33 | ^kallithea/public/css/select2x2\.png$ |
|
34 | 34 | ^kallithea/public/css/style\.css$ |
|
35 | 35 | ^kallithea/public/css/style\.css\.map$ |
|
36 | 36 | ^kallithea/public/js/bootstrap\.js$ |
|
37 | 37 | ^kallithea/public/js/dataTables\.bootstrap\.js$ |
|
38 | 38 | ^kallithea/public/js/jquery\.atwho\.min\.js$ |
|
39 | 39 | ^kallithea/public/js/jquery\.caret\.min\.js$ |
|
40 | 40 | ^kallithea/public/js/jquery\.dataTables\.js$ |
|
41 | 41 | ^kallithea/public/js/jquery\.flot\.js$ |
|
42 | 42 | ^kallithea/public/js/jquery\.flot\.selection\.js$ |
|
43 | 43 | ^kallithea/public/js/jquery\.flot\.time\.js$ |
|
44 | 44 | ^kallithea/public/js/jquery\.min\.js$ |
|
45 | 45 | ^kallithea/public/js/select2\.js$ |
|
46 | 46 | ^kallithea\.db$ |
|
47 | 47 | ^test\.db$ |
|
48 | 48 | ^Kallithea\.egg-info$ |
|
49 | 49 | ^my\.ini$ |
|
50 | 50 | ^fabfile\.py$ |
|
51 | 51 | ^\.idea$ |
|
52 | 52 | ^\.cache$ |
|
53 | 53 | ^\.pytest_cache$ |
|
54 | 54 | ^venv$ |
|
55 | 55 | /__pycache__$ |
|
56 | ^deps\.dot$ | |
|
57 | ^deps\.svg$ | |
|
58 | ^deps\.txt$ |
@@ -1,309 +1,397 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 | Please use the `mailing list`_ to send patches or report issues. |
|
19 | 19 | |
|
20 | 20 | We use Weblate_ to translate the user interface messages into languages other |
|
21 | 21 | than English. Join our project on `Hosted Weblate`_ to help us. |
|
22 | 22 | To register, you can use your Bitbucket or GitHub account. See :ref:`translations` |
|
23 | 23 | for more details. |
|
24 | 24 | |
|
25 | 25 | |
|
26 | 26 | Getting started |
|
27 | 27 | --------------- |
|
28 | 28 | |
|
29 | 29 | To get started with Kallithea development run the following commands in your |
|
30 | 30 | bash shell:: |
|
31 | 31 | |
|
32 | 32 | hg clone https://kallithea-scm.org/repos/kallithea |
|
33 | 33 | cd kallithea |
|
34 | 34 | python3 -m venv venv |
|
35 | 35 | . venv/bin/activate |
|
36 | 36 | pip install --upgrade pip setuptools |
|
37 | 37 | pip install --upgrade -e . -r dev_requirements.txt python-ldap python-pam |
|
38 | 38 | kallithea-cli config-create my.ini |
|
39 | 39 | kallithea-cli db-create -c my.ini --user=user --email=user@example.com --password=password --repos=/tmp |
|
40 | 40 | kallithea-cli front-end-build |
|
41 | 41 | gearbox serve -c my.ini --reload & |
|
42 | 42 | firefox http://127.0.0.1:5000/ |
|
43 | 43 | |
|
44 | 44 | |
|
45 | 45 | Contribution flow |
|
46 | 46 | ----------------- |
|
47 | 47 | |
|
48 | 48 | Starting from an existing Kallithea clone, make sure it is up to date with the |
|
49 | 49 | latest upstream changes:: |
|
50 | 50 | |
|
51 | 51 | hg pull |
|
52 | 52 | hg update |
|
53 | 53 | |
|
54 | 54 | Review the :ref:`contributing-guidelines` and :ref:`coding-guidelines`. |
|
55 | 55 | |
|
56 | 56 | If you are new to Mercurial, refer to Mercurial `Quick Start`_ and `Beginners |
|
57 | 57 | Guide`_ on the Mercurial wiki. |
|
58 | 58 | |
|
59 | 59 | Now, make some changes and test them (see :ref:`contributing-tests`). Don't |
|
60 | 60 | forget to add new tests to cover new functionality or bug fixes. |
|
61 | 61 | |
|
62 | 62 | For documentation changes, run ``make html`` from the ``docs`` directory to |
|
63 | 63 | generate the HTML result, then review them in your browser. |
|
64 | 64 | |
|
65 | 65 | Before submitting any changes, run the cleanup script:: |
|
66 | 66 | |
|
67 | 67 | ./scripts/run-all-cleanup |
|
68 | 68 | |
|
69 | 69 | When you are completely ready, you can send your changes to the community for |
|
70 | 70 | review and inclusion, via the mailing list (via ``hg email``). |
|
71 | 71 | |
|
72 | 72 | .. _contributing-tests: |
|
73 | 73 | |
|
74 | 74 | |
|
75 | Internal dependencies | |
|
76 | --------------------- | |
|
77 | ||
|
78 | We try to keep the code base clean and modular and avoid circular dependencies. | |
|
79 | Code should only invoke code in layers below itself. | |
|
80 | ||
|
81 | Imports should import whole modules ``from`` their parent module, perhaps | |
|
82 | ``as`` a shortened name. Avoid imports ``from`` modules. | |
|
83 | ||
|
84 | To avoid cycles and partially initialized modules, ``__init__.py`` should *not* | |
|
85 | contain any non-trivial imports. The top level of a module should *not* be a | |
|
86 | facade for the module functionality. | |
|
87 | ||
|
88 | Common code for a module is often in ``base.py``. | |
|
89 | ||
|
90 | The important part of the dependency graph is approximately linear. In the | |
|
91 | following list, modules may only depend on modules below them: | |
|
92 | ||
|
93 | ``tests`` | |
|
94 | Just get the job done - anything goes. | |
|
95 | ||
|
96 | ``bin/`` & ``config/`` & ``alembic/`` | |
|
97 | The main entry points, defined in ``setup.py``. Note: The TurboGears template | |
|
98 | use ``config`` for the high WSGI application - this is not for low level | |
|
99 | configuration. | |
|
100 | ||
|
101 | ``controllers/`` | |
|
102 | The top level web application, with TurboGears using the ``root`` controller | |
|
103 | as entry point, and ``routing`` dispatching to other controllers. | |
|
104 | ||
|
105 | ``templates/**.html`` | |
|
106 | The "view", rendering to HTML. Invoked by controllers which can pass them | |
|
107 | anything from lower layers - especially ``helpers`` available as ``h`` will | |
|
108 | cut through all layers, and ``c`` gives access to global variables. | |
|
109 | ||
|
110 | ``lib/helpers.py`` | |
|
111 | High level helpers, exposing everything to templates as ``h``. It depends on | |
|
112 | everything and has a huge dependency chain, so it should not be used for | |
|
113 | anything else. TODO. | |
|
114 | ||
|
115 | ``controlles/base.py`` | |
|
116 | The base class of controllers, with lots of model knowledge. | |
|
117 | ||
|
118 | ``lib/auth.py`` | |
|
119 | All things related to authentication. TODO. | |
|
120 | ||
|
121 | ``lib/utils.py`` | |
|
122 | High level utils with lots of model knowledge. TODO. | |
|
123 | ||
|
124 | ``lib/hooks.py`` | |
|
125 | Hooks into "everything" to give centralized logging to database, cache | |
|
126 | invalidation, and extension handling. TODO. | |
|
127 | ||
|
128 | ``model/`` | |
|
129 | Convenience business logic wrappers around database models. | |
|
130 | ||
|
131 | ``model/db.py`` | |
|
132 | Defines the database schema and provides some additional logic. | |
|
133 | ||
|
134 | ``model/scm.py`` | |
|
135 | All things related to anything. TODO. | |
|
136 | ||
|
137 | SQLAlchemy | |
|
138 | Database session and transaction in thread-local variables. | |
|
139 | ||
|
140 | ``lib/utils2.py`` | |
|
141 | Low level utils specific to Kallithea. | |
|
142 | ||
|
143 | ``lib/webutils.py`` | |
|
144 | Low level generic utils with awareness of the TurboGears environment. | |
|
145 | ||
|
146 | TurboGears | |
|
147 | Request, response and state like i18n gettext in thread-local variables. | |
|
148 | External dependency with global state - usage should be minimized. | |
|
149 | ||
|
150 | ``lib/vcs/`` | |
|
151 | Previously an independent library. No awareness of web, database, or state. | |
|
152 | ||
|
153 | ``lib/*`` | |
|
154 | Various "pure" functionality not depending on anything else. | |
|
155 | ||
|
156 | ``__init__`` | |
|
157 | Very basic Kallithea constants - some of them are set very early based on ``.ini``. | |
|
158 | ||
|
159 | This is not exactly how it is right now, but we aim for something like that. | |
|
160 | Especially the areas marked as TODO have some problems that need untangling. | |
|
161 | ||
|
162 | ||
|
75 | 163 | Running tests |
|
76 | 164 | ------------- |
|
77 | 165 | |
|
78 | 166 | After finishing your changes make sure all tests pass cleanly. Run the testsuite |
|
79 | 167 | by invoking ``py.test`` from the project root:: |
|
80 | 168 | |
|
81 | 169 | py.test |
|
82 | 170 | |
|
83 | 171 | Note that on unix systems, the temporary directory (``/tmp`` or where |
|
84 | 172 | ``$TMPDIR`` points) must allow executable files; Git hooks must be executable, |
|
85 | 173 | and the test suite creates repositories in the temporary directory. Linux |
|
86 | 174 | systems with /tmp mounted noexec will thus fail. |
|
87 | 175 | |
|
88 | 176 | Tests can be run on PostgreSQL like:: |
|
89 | 177 | |
|
90 | 178 | sudo -u postgres createuser 'kallithea-test' --pwprompt # password password |
|
91 | 179 | sudo -u postgres createdb 'kallithea-test' --owner 'kallithea-test' |
|
92 | 180 | REUSE_TEST_DB='postgresql://kallithea-test:password@localhost/kallithea-test' py.test |
|
93 | 181 | |
|
94 | 182 | Tests can be run on MariaDB/MySQL like:: |
|
95 | 183 | |
|
96 | 184 | echo "GRANT ALL PRIVILEGES ON \`kallithea-test\`.* TO 'kallithea-test'@'localhost' IDENTIFIED BY 'password'" | sudo -u mysql mysql |
|
97 | 185 | TEST_DB='mysql://kallithea-test:password@localhost/kallithea-test?charset=utf8mb4' py.test |
|
98 | 186 | |
|
99 | 187 | You can also use ``tox`` to run the tests with all supported Python versions. |
|
100 | 188 | |
|
101 | 189 | When running tests, Kallithea generates a `test.ini` based on template values |
|
102 | 190 | in `kallithea/tests/conftest.py` and populates the SQLite database specified |
|
103 | 191 | there. |
|
104 | 192 | |
|
105 | 193 | It is possible to avoid recreating the full test database on each invocation of |
|
106 | 194 | the tests, thus eliminating the initial delay. To achieve this, run the tests as:: |
|
107 | 195 | |
|
108 | 196 | gearbox serve -c /tmp/kallithea-test-XXX/test.ini --pid-file=test.pid --daemon |
|
109 | 197 | KALLITHEA_WHOOSH_TEST_DISABLE=1 KALLITHEA_NO_TMP_PATH=1 py.test |
|
110 | 198 | kill -9 $(cat test.pid) |
|
111 | 199 | |
|
112 | 200 | In these commands, the following variables are used:: |
|
113 | 201 | |
|
114 | 202 | KALLITHEA_WHOOSH_TEST_DISABLE=1 - skip whoosh index building and tests |
|
115 | 203 | KALLITHEA_NO_TMP_PATH=1 - disable new temp path for tests, used mostly for testing_vcs_operations |
|
116 | 204 | |
|
117 | 205 | You can run individual tests by specifying their path as argument to py.test. |
|
118 | 206 | py.test also has many more options, see `py.test -h`. Some useful options |
|
119 | 207 | are:: |
|
120 | 208 | |
|
121 | 209 | -k EXPRESSION only run tests which match the given substring |
|
122 | 210 | expression. An expression is a python evaluable |
|
123 | 211 | expression where all names are substring-matched |
|
124 | 212 | against test names and their parent classes. Example: |
|
125 | 213 | -x, --exitfirst exit instantly on first error or failed test. |
|
126 | 214 | --lf rerun only the tests that failed at the last run (or |
|
127 | 215 | all if none failed) |
|
128 | 216 | --ff run all tests but run the last failures first. This |
|
129 | 217 | may re-order tests and thus lead to repeated fixture |
|
130 | 218 | setup/teardown |
|
131 | 219 | --pdb start the interactive Python debugger on errors. |
|
132 | 220 | -s, --capture=no don't capture stdout (any stdout output will be |
|
133 | 221 | printed immediately) |
|
134 | 222 | |
|
135 | 223 | Performance tests |
|
136 | 224 | ^^^^^^^^^^^^^^^^^ |
|
137 | 225 | |
|
138 | 226 | A number of performance tests are present in the test suite, but they are |
|
139 | 227 | not run in a standard test run. These tests are useful to |
|
140 | 228 | evaluate the impact of certain code changes with respect to performance. |
|
141 | 229 | |
|
142 | 230 | To run these tests:: |
|
143 | 231 | |
|
144 | 232 | env TEST_PERFORMANCE=1 py.test kallithea/tests/performance |
|
145 | 233 | |
|
146 | 234 | To analyze performance, you could install pytest-profiling_, which enables the |
|
147 | 235 | --profile and --profile-svg options to py.test. |
|
148 | 236 | |
|
149 | 237 | .. _pytest-profiling: https://github.com/manahl/pytest-plugins/tree/master/pytest-profiling |
|
150 | 238 | |
|
151 | 239 | .. _contributing-guidelines: |
|
152 | 240 | |
|
153 | 241 | |
|
154 | 242 | Contribution guidelines |
|
155 | 243 | ----------------------- |
|
156 | 244 | |
|
157 | 245 | Kallithea is GPLv3 and we assume all contributions are made by the |
|
158 | 246 | committer/contributor and under GPLv3 unless explicitly stated. We do care a |
|
159 | 247 | lot about preservation of copyright and license information for existing code |
|
160 | 248 | that is brought into the project. |
|
161 | 249 | |
|
162 | 250 | Contributions will be accepted in most formats -- such as commits hosted on your |
|
163 | 251 | own Kallithea instance, or patches sent by email to the `kallithea-general`_ |
|
164 | 252 | mailing list. |
|
165 | 253 | |
|
166 | 254 | Make sure to test your changes both manually and with the automatic tests |
|
167 | 255 | before posting. |
|
168 | 256 | |
|
169 | 257 | We care about quality and review and keeping a clean repository history. We |
|
170 | 258 | might give feedback that requests polishing contributions until they are |
|
171 | 259 | "perfect". We might also rebase and collapse and make minor adjustments to your |
|
172 | 260 | changes when we apply them. |
|
173 | 261 | |
|
174 | 262 | We try to make sure we have consensus on the direction the project is taking. |
|
175 | 263 | Everything non-sensitive should be discussed in public -- preferably on the |
|
176 | 264 | mailing list. We aim at having all non-trivial changes reviewed by at least |
|
177 | 265 | one other core developer before pushing. Obvious non-controversial changes will |
|
178 | 266 | be handled more casually. |
|
179 | 267 | |
|
180 | 268 | There is a main development branch ("default") which is generally stable so that |
|
181 | 269 | it can be (and is) used in production. There is also a "stable" branch that is |
|
182 | 270 | almost exclusively reserved for bug fixes or trivial changes. Experimental |
|
183 | 271 | changes should live elsewhere (for example in a pull request) until they are |
|
184 | 272 | ready. |
|
185 | 273 | |
|
186 | 274 | .. _coding-guidelines: |
|
187 | 275 | |
|
188 | 276 | |
|
189 | 277 | Coding guidelines |
|
190 | 278 | ----------------- |
|
191 | 279 | |
|
192 | 280 | We don't have a formal coding/formatting standard. We are currently using a mix |
|
193 | 281 | of Mercurial's (https://www.mercurial-scm.org/wiki/CodingStyle), pep8, and |
|
194 | 282 | consistency with existing code. Run ``scripts/run-all-cleanup`` before |
|
195 | 283 | committing to ensure some basic code formatting consistency. |
|
196 | 284 | |
|
197 | 285 | We support Python 3.6 and later. |
|
198 | 286 | |
|
199 | 287 | We try to support the most common modern web browsers. IE9 is still supported |
|
200 | 288 | to the extent it is feasible, IE8 is not. |
|
201 | 289 | |
|
202 | 290 | We primarily support Linux and OS X on the server side but Windows should also work. |
|
203 | 291 | |
|
204 | 292 | HTML templates should use 2 spaces for indentation ... but be pragmatic. We |
|
205 | 293 | should use templates cleverly and avoid duplication. We should use reasonable |
|
206 | 294 | semantic markup with element classes and IDs that can be used for styling and testing. |
|
207 | 295 | We should only use inline styles in places where it really is semantic (such as |
|
208 | 296 | ``display: none``). |
|
209 | 297 | |
|
210 | 298 | JavaScript must use ``;`` between/after statements. Indentation 4 spaces. Inline |
|
211 | 299 | multiline functions should be indented two levels -- one for the ``()`` and one for |
|
212 | 300 | ``{}``. |
|
213 | 301 | Variables holding jQuery objects should be named with a leading ``$``. |
|
214 | 302 | |
|
215 | 303 | Commit messages should have a leading short line summarizing the changes. For |
|
216 | 304 | bug fixes, put ``(Issue #123)`` at the end of this line. |
|
217 | 305 | |
|
218 | 306 | Use American English grammar and spelling overall. Use `English title case`_ for |
|
219 | 307 | page titles, button labels, headers, and 'labels' for fields in forms. |
|
220 | 308 | |
|
221 | 309 | .. _English title case: https://en.wikipedia.org/wiki/Capitalization#Title_case |
|
222 | 310 | |
|
223 | 311 | Template helpers (that is, everything in ``kallithea.lib.helpers``) |
|
224 | 312 | should only be referenced from templates. If you need to call a |
|
225 | 313 | helper from the Python code, consider moving the function somewhere |
|
226 | 314 | else (e.g. to the model). |
|
227 | 315 | |
|
228 | 316 | Notes on the SQLAlchemy session |
|
229 | 317 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
|
230 | 318 | |
|
231 | 319 | Each HTTP request runs inside an independent SQLAlchemy session (as well |
|
232 | 320 | as in an independent database transaction). ``Session`` is the session manager |
|
233 | 321 | and factory. ``Session()`` will create a new session on-demand or return the |
|
234 | 322 | current session for the active thread. Many database operations are methods on |
|
235 | 323 | such session instances. The session will generally be removed by |
|
236 | 324 | TurboGears automatically. |
|
237 | 325 | |
|
238 | 326 | Database model objects |
|
239 | 327 | (almost) always belong to a particular SQLAlchemy session, which means |
|
240 | 328 | that SQLAlchemy will ensure that they're kept in sync with the database |
|
241 | 329 | (but also means that they cannot be shared across requests). |
|
242 | 330 | |
|
243 | 331 | Objects can be added to the session using ``Session().add``, but this is |
|
244 | 332 | rarely needed: |
|
245 | 333 | |
|
246 | 334 | * When creating a database object by calling the constructor directly, |
|
247 | 335 | it must explicitly be added to the session. |
|
248 | 336 | |
|
249 | 337 | * When creating an object using a factory function (like |
|
250 | 338 | ``create_repo``), the returned object has already (by convention) |
|
251 | 339 | been added to the session, and should not be added again. |
|
252 | 340 | |
|
253 | 341 | * When getting an object from the session (via ``Session().query`` or |
|
254 | 342 | any of the utility functions that look up objects in the database), |
|
255 | 343 | it's already part of the session, and should not be added again. |
|
256 | 344 | SQLAlchemy monitors attribute modifications automatically for all |
|
257 | 345 | objects it knows about and syncs them to the database. |
|
258 | 346 | |
|
259 | 347 | SQLAlchemy also flushes changes to the database automatically; manually |
|
260 | 348 | calling ``Session().flush`` is usually only necessary when the Python |
|
261 | 349 | code needs the database to assign an "auto-increment" primary key ID to |
|
262 | 350 | a freshly created model object (before flushing, the ID attribute will |
|
263 | 351 | be ``None``). |
|
264 | 352 | |
|
265 | 353 | Debugging |
|
266 | 354 | ^^^^^^^^^ |
|
267 | 355 | |
|
268 | 356 | A good way to trace what Kallithea is doing is to keep an eye on the output on |
|
269 | 357 | stdout/stderr of the server process. Perhaps change ``my.ini`` to log at |
|
270 | 358 | ``DEBUG`` or ``INFO`` level, especially ``[logger_kallithea]``, but perhaps |
|
271 | 359 | also other loggers. It is often easier to add additional ``log`` or ``print`` |
|
272 | 360 | statements than to use a Python debugger. |
|
273 | 361 | |
|
274 | 362 | Sometimes it is simpler to disable ``errorpage.enabled`` and perhaps also |
|
275 | 363 | ``trace_errors.enable`` to expose raw errors instead of adding extra |
|
276 | 364 | processing. Enabling ``debug`` can be helpful for showing and exploring |
|
277 | 365 | tracebacks in the browser, but is also insecure and will add extra processing. |
|
278 | 366 | |
|
279 | 367 | TurboGears2 DebugBar |
|
280 | 368 | ^^^^^^^^^^^^^^^^^^^^ |
|
281 | 369 | |
|
282 | 370 | It is possible to enable the TurboGears2-provided DebugBar_, a toolbar overlayed |
|
283 | 371 | over the Kallithea web interface, allowing you to see: |
|
284 | 372 | |
|
285 | 373 | * timing information of the current request, including profiling information |
|
286 | 374 | * request data, including GET data, POST data, cookies, headers and environment |
|
287 | 375 | variables |
|
288 | 376 | * a list of executed database queries, including timing and result values |
|
289 | 377 | |
|
290 | 378 | DebugBar is only activated when ``debug = true`` is set in the configuration |
|
291 | 379 | file. This is important, because the DebugBar toolbar will be visible for all |
|
292 | 380 | users, and allow them to see information they should not be allowed to see. Like |
|
293 | 381 | is anyway the case for ``debug = true``, do not use this in production! |
|
294 | 382 | |
|
295 | 383 | To enable DebugBar, install ``tgext.debugbar`` and ``kajiki`` (typically via |
|
296 | 384 | ``pip``) and restart Kallithea (in debug mode). |
|
297 | 385 | |
|
298 | 386 | |
|
299 | 387 | Thank you for your contribution! |
|
300 | 388 | -------------------------------- |
|
301 | 389 | |
|
302 | 390 | |
|
303 | 391 | .. _Weblate: http://weblate.org/ |
|
304 | 392 | .. _mailing list: http://lists.sfconservancy.org/mailman/listinfo/kallithea-general |
|
305 | 393 | .. _kallithea-general: http://lists.sfconservancy.org/mailman/listinfo/kallithea-general |
|
306 | 394 | .. _Hosted Weblate: https://hosted.weblate.org/projects/kallithea/kallithea/ |
|
307 | 395 | .. _DebugBar: https://github.com/TurboGears/tgext.debugbar |
|
308 | 396 | .. _Quick Start: https://www.mercurial-scm.org/wiki/QuickStart |
|
309 | 397 | .. _Beginners Guide: https://www.mercurial-scm.org/wiki/BeginnersGuides |
@@ -1,14 +1,17 b'' | |||
|
1 | 1 | #!/bin/sh |
|
2 | 2 | |
|
3 | 3 | # Convenience script for running various idempotent source code cleanup scripts |
|
4 | 4 | |
|
5 | 5 | set -e |
|
6 | 6 | set -x |
|
7 | 7 | |
|
8 | hg files 'set:!binary()&grep("^#!.*python")' 'set:**.py' | xargs scripts/deps.py | |
|
9 | dot -Tsvg deps.dot > deps.svg | |
|
10 | ||
|
8 | 11 | scripts/docs-headings.py |
|
9 | 12 | scripts/generate-ini.py |
|
10 | 13 | scripts/whitespacecleanup.sh |
|
11 | 14 | hg files 'set:!binary()&grep("^#!.*python")' 'set:**.py' | xargs scripts/source_format.py |
|
12 | 15 | |
|
13 | 16 | hg files 'set:!binary()&grep("^#!.*python")' 'set:**.py' | xargs scripts/pyflakes |
|
14 | 17 | echo "no blocking problems found by $0" |
General Comments 0
You need to be logged in to leave comments.
Login now