Show More
@@ -1,316 +1,317 b'' | |||||
1 | .. _contributing: |
|
1 | .. _contributing: | |
2 |
|
2 | |||
3 | ========================= |
|
3 | ========================= | |
4 | Contributing to Kallithea |
|
4 | Contributing to Kallithea | |
5 | ========================= |
|
5 | ========================= | |
6 |
|
6 | |||
7 | Kallithea is developed and maintained by its users. Please join us and scratch |
|
7 | Kallithea is developed and maintained by its users. Please join us and scratch | |
8 | your own itch. |
|
8 | your own itch. | |
9 |
|
9 | |||
10 |
|
10 | |||
11 | Infrastructure |
|
11 | Infrastructure | |
12 | -------------- |
|
12 | -------------- | |
13 |
|
13 | |||
14 | The main repository is hosted on Our Own Kallithea (aka OOK) at |
|
14 | The main repository is hosted on Our Own Kallithea (aka OOK) at | |
15 | https://kallithea-scm.org/repos/kallithea/, our self-hosted instance |
|
15 | https://kallithea-scm.org/repos/kallithea/, our self-hosted instance | |
16 | of Kallithea. |
|
16 | of Kallithea. | |
17 |
|
17 | |||
18 | For now, we use Bitbucket_ for `pull requests`_ and `issue tracking`_. The |
|
18 | For now, we use Bitbucket_ for `pull requests`_ and `issue tracking`_. The | |
19 | issue tracker is for tracking bugs, not for support, discussion, or ideas -- |
|
19 | issue tracker is for tracking bugs, not for support, discussion, or ideas -- | |
20 | please use the `mailing list`_ or :ref:`IRC <readme>` to reach the community. |
|
20 | please use the `mailing list`_ or :ref:`IRC <readme>` to reach the community. | |
21 |
|
21 | |||
22 | We use Weblate_ to translate the user interface messages into languages other |
|
22 | We use Weblate_ to translate the user interface messages into languages other | |
23 | than English. Join our project on `Hosted Weblate`_ to help us. |
|
23 | than English. Join our project on `Hosted Weblate`_ to help us. | |
24 | To register, you can use your Bitbucket or GitHub account. See :ref:`translations` |
|
24 | To register, you can use your Bitbucket or GitHub account. See :ref:`translations` | |
25 | for more details. |
|
25 | for more details. | |
26 |
|
26 | |||
27 |
|
27 | |||
28 | Getting started |
|
28 | Getting started | |
29 | --------------- |
|
29 | --------------- | |
30 |
|
30 | |||
31 | To get started with Kallithea development:: |
|
31 | To get started with Kallithea development:: | |
32 |
|
32 | |||
33 | hg clone https://kallithea-scm.org/repos/kallithea |
|
33 | hg clone https://kallithea-scm.org/repos/kallithea | |
34 | cd kallithea |
|
34 | cd kallithea | |
35 | virtualenv ../kallithea-venv |
|
35 | virtualenv ../kallithea-venv | |
36 | source ../kallithea-venv/bin/activate |
|
36 | source ../kallithea-venv/bin/activate | |
37 | pip install --upgrade pip setuptools |
|
37 | pip install --upgrade pip setuptools | |
38 | pip install --upgrade -e . |
|
38 | pip install --upgrade -e . | |
39 | pip install --upgrade -r dev_requirements.txt |
|
39 | pip install --upgrade -r dev_requirements.txt | |
40 | npm install # install dependencies - both tools and data |
|
40 | npm install # install dependencies - both tools and data | |
41 | npm run less # for generating css from less |
|
41 | npm run less # for generating css from less | |
42 | gearbox make-config my.ini |
|
42 | gearbox make-config my.ini | |
43 | gearbox setup-db -c my.ini --user=user --email=user@example.com --password=password --repos=/tmp |
|
43 | gearbox setup-db -c my.ini --user=user --email=user@example.com --password=password --repos=/tmp | |
44 | gearbox serve -c my.ini --reload & |
|
44 | gearbox serve -c my.ini --reload & | |
45 | firefox http://127.0.0.1:5000/ |
|
45 | firefox http://127.0.0.1:5000/ | |
46 |
|
46 | |||
47 | If you plan to use Bitbucket_ for sending contributions, you can also fork |
|
47 | If you plan to use Bitbucket_ for sending contributions, you can also fork | |
48 | Kallithea on Bitbucket_ first (https://bitbucket.org/conservancy/kallithea) and |
|
48 | Kallithea on Bitbucket_ first (https://bitbucket.org/conservancy/kallithea) and | |
49 | then replace the clone step above by a clone of your fork. In this case, please |
|
49 | then replace the clone step above by a clone of your fork. In this case, please | |
50 | see :ref:`contributing-guidelines` below for configuring your fork correctly. |
|
50 | see :ref:`contributing-guidelines` below for configuring your fork correctly. | |
51 |
|
51 | |||
52 |
|
52 | |||
53 | Contribution flow |
|
53 | Contribution flow | |
54 | ----------------- |
|
54 | ----------------- | |
55 |
|
55 | |||
56 | Starting from an existing Kallithea clone, make sure it is up to date with the |
|
56 | Starting from an existing Kallithea clone, make sure it is up to date with the | |
57 | latest upstream changes:: |
|
57 | latest upstream changes:: | |
58 |
|
58 | |||
59 | hg pull |
|
59 | hg pull | |
60 | hg update |
|
60 | hg update | |
61 |
|
61 | |||
62 | Review the :ref:`contributing-guidelines` and :ref:`coding-guidelines`. |
|
62 | Review the :ref:`contributing-guidelines` and :ref:`coding-guidelines`. | |
63 |
|
63 | |||
64 | If you are new to Mercurial, refer to Mercurial `Quick Start`_ and `Beginners |
|
64 | If you are new to Mercurial, refer to Mercurial `Quick Start`_ and `Beginners | |
65 | Guide`_ on the Mercurial wiki. |
|
65 | Guide`_ on the Mercurial wiki. | |
66 |
|
66 | |||
67 | Now, make some changes and test them (see :ref:`contributing-tests`). Don't |
|
67 | Now, make some changes and test them (see :ref:`contributing-tests`). Don't | |
68 | forget to add new tests to cover new functionality or bug fixes. |
|
68 | forget to add new tests to cover new functionality or bug fixes. | |
69 |
|
69 | |||
70 | For documentation changes, run ``make html`` from the ``docs`` directory to |
|
70 | For documentation changes, run ``make html`` from the ``docs`` directory to | |
71 | generate the HTML result, then review them in your browser. |
|
71 | generate the HTML result, then review them in your browser. | |
72 |
|
72 | |||
73 | Before submitting any changes, run the cleanup script:: |
|
73 | Before submitting any changes, run the cleanup script:: | |
74 |
|
74 | |||
75 | ./scripts/run-all-cleanup |
|
75 | ./scripts/run-all-cleanup | |
76 |
|
76 | |||
77 | When you are completely ready, you can send your changes to the community for |
|
77 | When you are completely ready, you can send your changes to the community for | |
78 | review and inclusion. Most commonly used methods are sending patches to the |
|
78 | review and inclusion. Most commonly used methods are sending patches to the | |
79 | mailing list (via ``hg email``) or by creating a pull request on Bitbucket_. |
|
79 | mailing list (via ``hg email``) or by creating a pull request on Bitbucket_. | |
80 |
|
80 | |||
81 | .. _contributing-tests: |
|
81 | .. _contributing-tests: | |
82 |
|
82 | |||
83 |
|
83 | |||
84 | Running tests |
|
84 | Running tests | |
85 | ------------- |
|
85 | ------------- | |
86 |
|
86 | |||
87 | After finishing your changes make sure all tests pass cleanly. Run the testsuite |
|
87 | After finishing your changes make sure all tests pass cleanly. Run the testsuite | |
88 | by invoking ``py.test`` from the project root:: |
|
88 | by invoking ``py.test`` from the project root:: | |
89 |
|
89 | |||
90 | py.test |
|
90 | py.test | |
91 |
|
91 | |||
92 | Note that testing on Python 2.6 also requires ``unittest2``. |
|
92 | Note that testing on Python 2.6 also requires ``unittest2``. | |
93 |
|
93 | |||
94 | Note that on unix systems, the temporary directory (``/tmp`` or where |
|
94 | Note that on unix systems, the temporary directory (``/tmp`` or where | |
95 | ``$TMPDIR`` points) must allow executable files; Git hooks must be executable, |
|
95 | ``$TMPDIR`` points) must allow executable files; Git hooks must be executable, | |
96 | and the test suite creates repositories in the temporary directory. Linux |
|
96 | and the test suite creates repositories in the temporary directory. Linux | |
97 | systems with /tmp mounted noexec will thus fail. |
|
97 | systems with /tmp mounted noexec will thus fail. | |
98 |
|
98 | |||
99 | You can also use ``tox`` to run the tests with all supported Python versions |
|
99 | You can also use ``tox`` to run the tests with all supported Python versions | |
100 | (currently Python 2.6--2.7). |
|
100 | (currently Python 2.6--2.7). | |
101 |
|
101 | |||
102 |
When running tests, Kallithea |
|
102 | When running tests, Kallithea generates a `test.ini` based on template values | |
103 | SQLite database specified there. |
|
103 | in `kallithea/tests/conftest.py` and populates the SQLite database specified | |
|
104 | there. | |||
104 |
|
105 | |||
105 | It is possible to avoid recreating the full test database on each invocation of |
|
106 | It is possible to avoid recreating the full test database on each invocation of | |
106 | the tests, thus eliminating the initial delay. To achieve this, run the tests as:: |
|
107 | the tests, thus eliminating the initial delay. To achieve this, run the tests as:: | |
107 |
|
108 | |||
108 |
gearbox serve -c kallithea |
|
109 | gearbox serve -c /tmp/kallithea-test-XXX/test.ini --pid-file=test.pid --daemon | |
109 | KALLITHEA_WHOOSH_TEST_DISABLE=1 KALLITHEA_NO_TMP_PATH=1 py.test |
|
110 | KALLITHEA_WHOOSH_TEST_DISABLE=1 KALLITHEA_NO_TMP_PATH=1 py.test | |
110 | kill -9 $(cat test.pid) |
|
111 | kill -9 $(cat test.pid) | |
111 |
|
112 | |||
112 | In these commands, the following variables are used:: |
|
113 | In these commands, the following variables are used:: | |
113 |
|
114 | |||
114 | KALLITHEA_WHOOSH_TEST_DISABLE=1 - skip whoosh index building and tests |
|
115 | KALLITHEA_WHOOSH_TEST_DISABLE=1 - skip whoosh index building and tests | |
115 | KALLITHEA_NO_TMP_PATH=1 - disable new temp path for tests, used mostly for testing_vcs_operations |
|
116 | KALLITHEA_NO_TMP_PATH=1 - disable new temp path for tests, used mostly for testing_vcs_operations | |
116 |
|
117 | |||
117 | You can run individual tests by specifying their path as argument to py.test. |
|
118 | You can run individual tests by specifying their path as argument to py.test. | |
118 | py.test also has many more options, see `py.test -h`. Some useful options |
|
119 | py.test also has many more options, see `py.test -h`. Some useful options | |
119 | are:: |
|
120 | are:: | |
120 |
|
121 | |||
121 | -k EXPRESSION only run tests which match the given substring |
|
122 | -k EXPRESSION only run tests which match the given substring | |
122 | expression. An expression is a python evaluable |
|
123 | expression. An expression is a python evaluable | |
123 | expression where all names are substring-matched |
|
124 | expression where all names are substring-matched | |
124 | against test names and their parent classes. Example: |
|
125 | against test names and their parent classes. Example: | |
125 | -x, --exitfirst exit instantly on first error or failed test. |
|
126 | -x, --exitfirst exit instantly on first error or failed test. | |
126 | --lf rerun only the tests that failed at the last run (or |
|
127 | --lf rerun only the tests that failed at the last run (or | |
127 | all if none failed) |
|
128 | all if none failed) | |
128 | --ff run all tests but run the last failures first. This |
|
129 | --ff run all tests but run the last failures first. This | |
129 | may re-order tests and thus lead to repeated fixture |
|
130 | may re-order tests and thus lead to repeated fixture | |
130 | setup/teardown |
|
131 | setup/teardown | |
131 | --pdb start the interactive Python debugger on errors. |
|
132 | --pdb start the interactive Python debugger on errors. | |
132 | -s, --capture=no don't capture stdout (any stdout output will be |
|
133 | -s, --capture=no don't capture stdout (any stdout output will be | |
133 | printed immediately) |
|
134 | printed immediately) | |
134 |
|
135 | |||
135 | Performance tests |
|
136 | Performance tests | |
136 | ^^^^^^^^^^^^^^^^^ |
|
137 | ^^^^^^^^^^^^^^^^^ | |
137 |
|
138 | |||
138 | A number of performance tests are present in the test suite, but they are |
|
139 | A number of performance tests are present in the test suite, but they are | |
139 | not run in a standard test run. These tests are useful to |
|
140 | not run in a standard test run. These tests are useful to | |
140 | evaluate the impact of certain code changes with respect to performance. |
|
141 | evaluate the impact of certain code changes with respect to performance. | |
141 |
|
142 | |||
142 | To run these tests:: |
|
143 | To run these tests:: | |
143 |
|
144 | |||
144 | env TEST_PERFORMANCE=1 py.test kallithea/tests/performance |
|
145 | env TEST_PERFORMANCE=1 py.test kallithea/tests/performance | |
145 |
|
146 | |||
146 | To analyze performance, you could install pytest-profiling_, which enables the |
|
147 | To analyze performance, you could install pytest-profiling_, which enables the | |
147 | --profile and --profile-svg options to py.test. |
|
148 | --profile and --profile-svg options to py.test. | |
148 |
|
149 | |||
149 | .. _pytest-profiling: https://github.com/manahl/pytest-plugins/tree/master/pytest-profiling |
|
150 | .. _pytest-profiling: https://github.com/manahl/pytest-plugins/tree/master/pytest-profiling | |
150 |
|
151 | |||
151 | .. _contributing-guidelines: |
|
152 | .. _contributing-guidelines: | |
152 |
|
153 | |||
153 |
|
154 | |||
154 | Contribution guidelines |
|
155 | Contribution guidelines | |
155 | ----------------------- |
|
156 | ----------------------- | |
156 |
|
157 | |||
157 | Kallithea is GPLv3 and we assume all contributions are made by the |
|
158 | Kallithea is GPLv3 and we assume all contributions are made by the | |
158 | committer/contributor and under GPLv3 unless explicitly stated. We do care a |
|
159 | committer/contributor and under GPLv3 unless explicitly stated. We do care a | |
159 | lot about preservation of copyright and license information for existing code |
|
160 | lot about preservation of copyright and license information for existing code | |
160 | that is brought into the project. |
|
161 | that is brought into the project. | |
161 |
|
162 | |||
162 | Contributions will be accepted in most formats -- such as pull requests on |
|
163 | Contributions will be accepted in most formats -- such as pull requests on | |
163 | Bitbucket, something hosted on your own Kallithea instance, or patches sent by |
|
164 | Bitbucket, something hosted on your own Kallithea instance, or patches sent by | |
164 | email to the `kallithea-general`_ mailing list. |
|
165 | email to the `kallithea-general`_ mailing list. | |
165 |
|
166 | |||
166 | When contributing via Bitbucket, please make your fork of |
|
167 | When contributing via Bitbucket, please make your fork of | |
167 | https://bitbucket.org/conservancy/kallithea/ `non-publishing`_ -- it is one of |
|
168 | https://bitbucket.org/conservancy/kallithea/ `non-publishing`_ -- it is one of | |
168 | the settings on "Repository details" page. This ensures your commits are in |
|
169 | the settings on "Repository details" page. This ensures your commits are in | |
169 | "draft" phase and makes it easier for you to address feedback and for project |
|
170 | "draft" phase and makes it easier for you to address feedback and for project | |
170 | maintainers to integrate your changes. |
|
171 | maintainers to integrate your changes. | |
171 |
|
172 | |||
172 | .. _non-publishing: https://www.mercurial-scm.org/wiki/Phases#Publishing_Repository |
|
173 | .. _non-publishing: https://www.mercurial-scm.org/wiki/Phases#Publishing_Repository | |
173 |
|
174 | |||
174 | Make sure to test your changes both manually and with the automatic tests |
|
175 | Make sure to test your changes both manually and with the automatic tests | |
175 | before posting. |
|
176 | before posting. | |
176 |
|
177 | |||
177 | We care about quality and review and keeping a clean repository history. We |
|
178 | We care about quality and review and keeping a clean repository history. We | |
178 | might give feedback that requests polishing contributions until they are |
|
179 | might give feedback that requests polishing contributions until they are | |
179 | "perfect". We might also rebase and collapse and make minor adjustments to your |
|
180 | "perfect". We might also rebase and collapse and make minor adjustments to your | |
180 | changes when we apply them. |
|
181 | changes when we apply them. | |
181 |
|
182 | |||
182 | We try to make sure we have consensus on the direction the project is taking. |
|
183 | We try to make sure we have consensus on the direction the project is taking. | |
183 | Everything non-sensitive should be discussed in public -- preferably on the |
|
184 | Everything non-sensitive should be discussed in public -- preferably on the | |
184 | mailing list. We aim at having all non-trivial changes reviewed by at least |
|
185 | mailing list. We aim at having all non-trivial changes reviewed by at least | |
185 | one other core developer before pushing. Obvious non-controversial changes will |
|
186 | one other core developer before pushing. Obvious non-controversial changes will | |
186 | be handled more casually. |
|
187 | be handled more casually. | |
187 |
|
188 | |||
188 | There is a main development branch ("default") which is generally stable so that |
|
189 | There is a main development branch ("default") which is generally stable so that | |
189 | it can be (and is) used in production. There is also a "stable" branch that is |
|
190 | it can be (and is) used in production. There is also a "stable" branch that is | |
190 | almost exclusively reserved for bug fixes or trivial changes. Experimental |
|
191 | almost exclusively reserved for bug fixes or trivial changes. Experimental | |
191 | changes should live elsewhere (for example in a pull request) until they are |
|
192 | changes should live elsewhere (for example in a pull request) until they are | |
192 | ready. |
|
193 | ready. | |
193 |
|
194 | |||
194 | .. _coding-guidelines: |
|
195 | .. _coding-guidelines: | |
195 |
|
196 | |||
196 |
|
197 | |||
197 | Coding guidelines |
|
198 | Coding guidelines | |
198 | ----------------- |
|
199 | ----------------- | |
199 |
|
200 | |||
200 | We don't have a formal coding/formatting standard. We are currently using a mix |
|
201 | We don't have a formal coding/formatting standard. We are currently using a mix | |
201 | of Mercurial's (https://www.mercurial-scm.org/wiki/CodingStyle), pep8, and |
|
202 | of Mercurial's (https://www.mercurial-scm.org/wiki/CodingStyle), pep8, and | |
202 | consistency with existing code. Run ``scripts/run-all-cleanup`` before |
|
203 | consistency with existing code. Run ``scripts/run-all-cleanup`` before | |
203 | committing to ensure some basic code formatting consistency. |
|
204 | committing to ensure some basic code formatting consistency. | |
204 |
|
205 | |||
205 | We support both Python 2.6.x and 2.7.x and nothing else. For now we don't care |
|
206 | We support both Python 2.6.x and 2.7.x and nothing else. For now we don't care | |
206 | about Python 3 compatibility. |
|
207 | about Python 3 compatibility. | |
207 |
|
208 | |||
208 | We try to support the most common modern web browsers. IE9 is still supported |
|
209 | We try to support the most common modern web browsers. IE9 is still supported | |
209 | to the extent it is feasible, IE8 is not. |
|
210 | to the extent it is feasible, IE8 is not. | |
210 |
|
211 | |||
211 | We primarily support Linux and OS X on the server side but Windows should also work. |
|
212 | We primarily support Linux and OS X on the server side but Windows should also work. | |
212 |
|
213 | |||
213 | HTML templates should use 2 spaces for indentation ... but be pragmatic. We |
|
214 | HTML templates should use 2 spaces for indentation ... but be pragmatic. We | |
214 | should use templates cleverly and avoid duplication. We should use reasonable |
|
215 | should use templates cleverly and avoid duplication. We should use reasonable | |
215 | semantic markup with element classes and IDs that can be used for styling and testing. |
|
216 | semantic markup with element classes and IDs that can be used for styling and testing. | |
216 | We should only use inline styles in places where it really is semantic (such as |
|
217 | We should only use inline styles in places where it really is semantic (such as | |
217 | ``display: none``). |
|
218 | ``display: none``). | |
218 |
|
219 | |||
219 | JavaScript must use ``;`` between/after statements. Indentation 4 spaces. Inline |
|
220 | JavaScript must use ``;`` between/after statements. Indentation 4 spaces. Inline | |
220 | multiline functions should be indented two levels -- one for the ``()`` and one for |
|
221 | multiline functions should be indented two levels -- one for the ``()`` and one for | |
221 | ``{}``. |
|
222 | ``{}``. | |
222 | Variables holding jQuery objects should be named with a leading ``$``. |
|
223 | Variables holding jQuery objects should be named with a leading ``$``. | |
223 |
|
224 | |||
224 | Commit messages should have a leading short line summarizing the changes. For |
|
225 | Commit messages should have a leading short line summarizing the changes. For | |
225 | bug fixes, put ``(Issue #123)`` at the end of this line. |
|
226 | bug fixes, put ``(Issue #123)`` at the end of this line. | |
226 |
|
227 | |||
227 | Use American English grammar and spelling overall. Use `English title case`_ for |
|
228 | Use American English grammar and spelling overall. Use `English title case`_ for | |
228 | page titles, button labels, headers, and 'labels' for fields in forms. |
|
229 | page titles, button labels, headers, and 'labels' for fields in forms. | |
229 |
|
230 | |||
230 | .. _English title case: https://en.wikipedia.org/wiki/Capitalization#Title_case |
|
231 | .. _English title case: https://en.wikipedia.org/wiki/Capitalization#Title_case | |
231 |
|
232 | |||
232 | Template helpers (that is, everything in ``kallithea.lib.helpers``) |
|
233 | Template helpers (that is, everything in ``kallithea.lib.helpers``) | |
233 | should only be referenced from templates. If you need to call a |
|
234 | should only be referenced from templates. If you need to call a | |
234 | helper from the Python code, consider moving the function somewhere |
|
235 | helper from the Python code, consider moving the function somewhere | |
235 | else (e.g. to the model). |
|
236 | else (e.g. to the model). | |
236 |
|
237 | |||
237 | Notes on the SQLAlchemy session |
|
238 | Notes on the SQLAlchemy session | |
238 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
|
239 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
239 |
|
240 | |||
240 | Each HTTP request runs inside an independent SQLAlchemy session (as well |
|
241 | Each HTTP request runs inside an independent SQLAlchemy session (as well | |
241 | as in an independent database transaction). ``Session`` is the session manager |
|
242 | as in an independent database transaction). ``Session`` is the session manager | |
242 | and factory. ``Session()`` will create a new session on-demand or return the |
|
243 | and factory. ``Session()`` will create a new session on-demand or return the | |
243 | current session for the active thread. Many database operations are methods on |
|
244 | current session for the active thread. Many database operations are methods on | |
244 | such session instances - only ``Session.remove()`` should be called directly on |
|
245 | such session instances - only ``Session.remove()`` should be called directly on | |
245 | the manager. |
|
246 | the manager. | |
246 |
|
247 | |||
247 | Database model objects |
|
248 | Database model objects | |
248 | (almost) always belong to a particular SQLAlchemy session, which means |
|
249 | (almost) always belong to a particular SQLAlchemy session, which means | |
249 | that SQLAlchemy will ensure that they're kept in sync with the database |
|
250 | that SQLAlchemy will ensure that they're kept in sync with the database | |
250 | (but also means that they cannot be shared across requests). |
|
251 | (but also means that they cannot be shared across requests). | |
251 |
|
252 | |||
252 | Objects can be added to the session using ``Session().add``, but this is |
|
253 | Objects can be added to the session using ``Session().add``, but this is | |
253 | rarely needed: |
|
254 | rarely needed: | |
254 |
|
255 | |||
255 | * When creating a database object by calling the constructor directly, |
|
256 | * When creating a database object by calling the constructor directly, | |
256 | it must explicitly be added to the session. |
|
257 | it must explicitly be added to the session. | |
257 |
|
258 | |||
258 | * When creating an object using a factory function (like |
|
259 | * When creating an object using a factory function (like | |
259 | ``create_repo``), the returned object has already (by convention) |
|
260 | ``create_repo``), the returned object has already (by convention) | |
260 | been added to the session, and should not be added again. |
|
261 | been added to the session, and should not be added again. | |
261 |
|
262 | |||
262 | * When getting an object from the session (via ``Session().query`` or |
|
263 | * When getting an object from the session (via ``Session().query`` or | |
263 | any of the utility functions that look up objects in the database), |
|
264 | any of the utility functions that look up objects in the database), | |
264 | it's already part of the session, and should not be added again. |
|
265 | it's already part of the session, and should not be added again. | |
265 | SQLAlchemy monitors attribute modifications automatically for all |
|
266 | SQLAlchemy monitors attribute modifications automatically for all | |
266 | objects it knows about and syncs them to the database. |
|
267 | objects it knows about and syncs them to the database. | |
267 |
|
268 | |||
268 | SQLAlchemy also flushes changes to the database automatically; manually |
|
269 | SQLAlchemy also flushes changes to the database automatically; manually | |
269 | calling ``Session().flush`` is usually only necessary when the Python |
|
270 | calling ``Session().flush`` is usually only necessary when the Python | |
270 | code needs the database to assign an "auto-increment" primary key ID to |
|
271 | code needs the database to assign an "auto-increment" primary key ID to | |
271 | a freshly created model object (before flushing, the ID attribute will |
|
272 | a freshly created model object (before flushing, the ID attribute will | |
272 | be ``None``). |
|
273 | be ``None``). | |
273 |
|
274 | |||
274 | TurboGears2 DebugBar |
|
275 | TurboGears2 DebugBar | |
275 | ^^^^^^^^^^^^^^^^^^^^ |
|
276 | ^^^^^^^^^^^^^^^^^^^^ | |
276 |
|
277 | |||
277 | It is possible to enable the TurboGears2-provided DebugBar_, a toolbar overlayed |
|
278 | It is possible to enable the TurboGears2-provided DebugBar_, a toolbar overlayed | |
278 | over the Kallithea web interface, allowing you to see: |
|
279 | over the Kallithea web interface, allowing you to see: | |
279 |
|
280 | |||
280 | * timing information of the current request, including profiling information |
|
281 | * timing information of the current request, including profiling information | |
281 | * request data, including GET data, POST data, cookies, headers and environment |
|
282 | * request data, including GET data, POST data, cookies, headers and environment | |
282 | variables |
|
283 | variables | |
283 | * a list of executed database queries, including timing and result values |
|
284 | * a list of executed database queries, including timing and result values | |
284 |
|
285 | |||
285 | DebugBar is only activated when ``debug = true`` is set in the configuration |
|
286 | DebugBar is only activated when ``debug = true`` is set in the configuration | |
286 | file. This is important, because the DebugBar toolbar will be visible for all |
|
287 | file. This is important, because the DebugBar toolbar will be visible for all | |
287 | users, and allow them to see information they should not be allowed to see. Like |
|
288 | users, and allow them to see information they should not be allowed to see. Like | |
288 | is anyway the case for ``debug = true``, do not use this in production! |
|
289 | is anyway the case for ``debug = true``, do not use this in production! | |
289 |
|
290 | |||
290 | To enable DebugBar, install ``tgext.debugbar`` and ``kajiki`` (typically via |
|
291 | To enable DebugBar, install ``tgext.debugbar`` and ``kajiki`` (typically via | |
291 | ``pip``) and restart Kallithea (in debug mode). |
|
292 | ``pip``) and restart Kallithea (in debug mode). | |
292 |
|
293 | |||
293 |
|
294 | |||
294 | "Roadmap" |
|
295 | "Roadmap" | |
295 | --------- |
|
296 | --------- | |
296 |
|
297 | |||
297 | We do not have a road map but are waiting for your contributions. Refer to the |
|
298 | We do not have a road map but are waiting for your contributions. Refer to the | |
298 | wiki_ for some ideas of places we might want to go -- contributions in these |
|
299 | wiki_ for some ideas of places we might want to go -- contributions in these | |
299 | areas are very welcome. |
|
300 | areas are very welcome. | |
300 |
|
301 | |||
301 |
|
302 | |||
302 | Thank you for your contribution! |
|
303 | Thank you for your contribution! | |
303 | -------------------------------- |
|
304 | -------------------------------- | |
304 |
|
305 | |||
305 |
|
306 | |||
306 | .. _Weblate: http://weblate.org/ |
|
307 | .. _Weblate: http://weblate.org/ | |
307 | .. _issue tracking: https://bitbucket.org/conservancy/kallithea/issues?status=new&status=open |
|
308 | .. _issue tracking: https://bitbucket.org/conservancy/kallithea/issues?status=new&status=open | |
308 | .. _pull requests: https://bitbucket.org/conservancy/kallithea/pull-requests |
|
309 | .. _pull requests: https://bitbucket.org/conservancy/kallithea/pull-requests | |
309 | .. _bitbucket: http://bitbucket.org/ |
|
310 | .. _bitbucket: http://bitbucket.org/ | |
310 | .. _mailing list: http://lists.sfconservancy.org/mailman/listinfo/kallithea-general |
|
311 | .. _mailing list: http://lists.sfconservancy.org/mailman/listinfo/kallithea-general | |
311 | .. _kallithea-general: http://lists.sfconservancy.org/mailman/listinfo/kallithea-general |
|
312 | .. _kallithea-general: http://lists.sfconservancy.org/mailman/listinfo/kallithea-general | |
312 | .. _Hosted Weblate: https://hosted.weblate.org/projects/kallithea/kallithea/ |
|
313 | .. _Hosted Weblate: https://hosted.weblate.org/projects/kallithea/kallithea/ | |
313 | .. _wiki: https://bitbucket.org/conservancy/kallithea/wiki/Home |
|
314 | .. _wiki: https://bitbucket.org/conservancy/kallithea/wiki/Home | |
314 | .. _DebugBar: https://github.com/TurboGears/tgext.debugbar |
|
315 | .. _DebugBar: https://github.com/TurboGears/tgext.debugbar | |
315 | .. _Quick Start: https://www.mercurial-scm.org/wiki/QuickStart |
|
316 | .. _Quick Start: https://www.mercurial-scm.org/wiki/QuickStart | |
316 | .. _Beginners Guide: https://www.mercurial-scm.org/wiki/BeginnersGuides |
|
317 | .. _Beginners Guide: https://www.mercurial-scm.org/wiki/BeginnersGuides |
@@ -1,100 +1,97 b'' | |||||
1 | ============ |
|
1 | ============ | |
2 | Translations |
|
2 | Translations | |
3 | ============ |
|
3 | ============ | |
4 |
|
4 | |||
5 | Translations are available on Hosted Weblate at the following URL: |
|
5 | Translations are available on Hosted Weblate at the following URL: | |
6 |
|
6 | |||
7 | https://hosted.weblate.org/projects/kallithea/kallithea/ |
|
7 | https://hosted.weblate.org/projects/kallithea/kallithea/ | |
8 |
|
8 | |||
9 | Registered users may contribute to the existing languages, or request a new |
|
9 | Registered users may contribute to the existing languages, or request a new | |
10 | language translations. |
|
10 | language translations. | |
11 |
|
11 | |||
12 |
|
12 | |||
13 | Translating using Weblate |
|
13 | Translating using Weblate | |
14 | ------------------------- |
|
14 | ------------------------- | |
15 |
|
15 | |||
16 | Weblate_ offers a simple and easy to use interface featuring glossary, machine |
|
16 | Weblate_ offers a simple and easy to use interface featuring glossary, machine | |
17 | translation, suggestions based on similar translations in other projects, |
|
17 | translation, suggestions based on similar translations in other projects, | |
18 | automatic checks etc. Weblate imports the source code tree directly from |
|
18 | automatic checks etc. Weblate imports the source code tree directly from | |
19 | the version control system, and commits edits back from time to time. |
|
19 | the version control system, and commits edits back from time to time. | |
20 |
|
20 | |||
21 | When registering at Weblate, make sure you name and email address you prefer to |
|
21 | When registering at Weblate, make sure you name and email address you prefer to | |
22 | be used when your changes are committed. We can and probably will amend changesets |
|
22 | be used when your changes are committed. We can and probably will amend changesets | |
23 | coming from Weblate, but having things right from the beginning makes things easier. |
|
23 | coming from Weblate, but having things right from the beginning makes things easier. | |
24 |
|
24 | |||
25 | Weblate performs sanity checks all the time and tries to prevent you from ignoring |
|
25 | Weblate performs sanity checks all the time and tries to prevent you from ignoring | |
26 | them. Most common mistakes are inconsistent punctuation, whitespaces, missing or extra |
|
26 | them. Most common mistakes are inconsistent punctuation, whitespaces, missing or extra | |
27 | format parameters, untranslated strings copied into the translation. Please perform |
|
27 | format parameters, untranslated strings copied into the translation. Please perform | |
28 | necessary corrections when they're needed, or override the false positives. |
|
28 | necessary corrections when they're needed, or override the false positives. | |
29 |
|
29 | |||
30 |
|
30 | |||
31 | Merging translations from Weblate |
|
31 | Merging translations from Weblate | |
32 | --------------------------------- |
|
32 | --------------------------------- | |
33 |
|
33 | |||
34 | Weblate rebases its changes every time it pulls from our repository. Pulls are triggered |
|
34 | Weblate rebases its changes every time it pulls from our repository. Pulls are triggered | |
35 | by a web hook from Our Own Kallithea every time it receives new commits. Usually merging |
|
35 | by a web hook from Our Own Kallithea every time it receives new commits. Usually merging | |
36 | the new translations is a straightforward process consisting of a pull from Weblate-hosted |
|
36 | the new translations is a straightforward process consisting of a pull from Weblate-hosted | |
37 | repository which is available under Data Exports tab in Weblate interface. |
|
37 | repository which is available under Data Exports tab in Weblate interface. | |
38 |
|
38 | |||
39 | Weblate tries to minimise the number of commits, but that's not always work, especially |
|
39 | Weblate tries to minimise the number of commits, but that's not always work, especially | |
40 | when two translators work with different languages at more or less the same time. |
|
40 | when two translators work with different languages at more or less the same time. | |
41 | It makes sense sometimes to re-order or fold commits by the same author when they touch |
|
41 | It makes sense sometimes to re-order or fold commits by the same author when they touch | |
42 | just the same language translation. That, however, may confuse Weblate sometimes, in |
|
42 | just the same language translation. That, however, may confuse Weblate sometimes, in | |
43 | which case it should be manually convinced it has to discard the commits it created by |
|
43 | which case it should be manually convinced it has to discard the commits it created by | |
44 | using its administrative interface. |
|
44 | using its administrative interface. | |
45 |
|
45 | |||
46 |
|
46 | |||
47 | Manual creation of a new language translation |
|
47 | Manual creation of a new language translation | |
48 | --------------------------------------------- |
|
48 | --------------------------------------------- | |
49 |
|
49 | |||
50 | In the prepared development environment, run the following to ensure |
|
50 | In the prepared development environment, run the following to ensure | |
51 | all translation strings are extracted and up-to-date:: |
|
51 | all translation strings are extracted and up-to-date:: | |
52 |
|
52 | |||
53 | python2 setup.py extract_messages |
|
53 | python2 setup.py extract_messages | |
54 |
|
54 | |||
55 | Create new language by executing following command:: |
|
55 | Create new language by executing following command:: | |
56 |
|
56 | |||
57 | python2 setup.py init_catalog -l <new_language_code> |
|
57 | python2 setup.py init_catalog -l <new_language_code> | |
58 |
|
58 | |||
59 | This creates a new translation under directory `kallithea/i18n/<new_language_code>` |
|
59 | This creates a new translation under directory `kallithea/i18n/<new_language_code>` | |
60 | based on the translation template file, `kallithea/i18n/kallithea.pot`. |
|
60 | based on the translation template file, `kallithea/i18n/kallithea.pot`. | |
61 |
|
61 | |||
62 | Edit the new PO file located in `LC_MESSAGES` directory with poedit or your |
|
62 | Edit the new PO file located in `LC_MESSAGES` directory with poedit or your | |
63 | favorite PO files editor. After you finished with the translations, check the |
|
63 | favorite PO files editor. After you finished with the translations, check the | |
64 | translation file for errors by executing:: |
|
64 | translation file for errors by executing:: | |
65 |
|
65 | |||
66 | msgfmt -f -c kallithea/i18n/<new_language_code>/LC_MESSAGES/<updated_file.po> |
|
66 | msgfmt -f -c kallithea/i18n/<new_language_code>/LC_MESSAGES/<updated_file.po> | |
67 |
|
67 | |||
68 | Finally, compile the translations:: |
|
68 | Finally, compile the translations:: | |
69 |
|
69 | |||
70 | python2 setup.py compile_catalog -l <new_language_code> |
|
70 | python2 setup.py compile_catalog -l <new_language_code> | |
71 |
|
71 | |||
72 |
|
72 | |||
73 | Updating translations |
|
73 | Updating translations | |
74 | --------------------- |
|
74 | --------------------- | |
75 |
|
75 | |||
76 | Extract the latest versions of strings for translation by running:: |
|
76 | Extract the latest versions of strings for translation by running:: | |
77 |
|
77 | |||
78 | python2 setup.py extract_messages |
|
78 | python2 setup.py extract_messages | |
79 |
|
79 | |||
80 | Update the PO file by doing:: |
|
80 | Update the PO file by doing:: | |
81 |
|
81 | |||
82 | python2 setup.py update_catalog -l <new_language_code> |
|
82 | python2 setup.py update_catalog -l <new_language_code> | |
83 |
|
83 | |||
84 | Edit the new updated translation file. Repeat all steps after `init_catalog` step from |
|
84 | Edit the new updated translation file. Repeat all steps after `init_catalog` step from | |
85 | new translation instructions |
|
85 | new translation instructions | |
86 |
|
86 | |||
87 |
|
87 | |||
88 | Testing translations |
|
88 | Testing translations | |
89 | -------------------- |
|
89 | -------------------- | |
90 |
|
90 | |||
91 | Edit kallithea/tests/test.ini file and set lang attribute to:: |
|
91 | Edit `kallithea/tests/conftest.py` and set `i18n.lang` to `<new_language_code>` | |
92 |
|
92 | and run Kallithea tests by executing:: | ||
93 | lang=<new_language_code> |
|
|||
94 |
|
||||
95 | Run Kallithea tests by executing:: |
|
|||
96 |
|
93 | |||
97 | py.test |
|
94 | py.test | |
98 |
|
95 | |||
99 |
|
96 | |||
100 | .. _Weblate: http://weblate.org/ |
|
97 | .. _Weblate: http://weblate.org/ |
@@ -1,210 +1,211 b'' | |||||
1 | import os |
|
1 | import os | |
2 | import re |
|
2 | import re | |
3 | import sys |
|
3 | import sys | |
4 | import logging |
|
4 | import logging | |
5 | import pkg_resources |
|
5 | import pkg_resources | |
6 | import time |
|
6 | import time | |
7 |
|
7 | |||
8 | import formencode |
|
8 | import formencode | |
9 | from paste.deploy import loadwsgi |
|
9 | from paste.deploy import loadwsgi | |
10 | from routes.util import URLGenerator |
|
10 | from routes.util import URLGenerator | |
11 | import pytest |
|
11 | import pytest | |
12 | from pytest_localserver.http import WSGIServer |
|
12 | from pytest_localserver.http import WSGIServer | |
13 |
|
13 | |||
14 | from kallithea.controllers.root import RootController |
|
14 | from kallithea.controllers.root import RootController | |
15 | from kallithea.lib import inifile |
|
15 | from kallithea.lib import inifile | |
16 | from kallithea.lib.utils import repo2db_mapper |
|
16 | from kallithea.lib.utils import repo2db_mapper | |
17 | from kallithea.model.user import UserModel |
|
17 | from kallithea.model.user import UserModel | |
18 | from kallithea.model.meta import Session |
|
18 | from kallithea.model.meta import Session | |
19 | from kallithea.model.db import Setting, User, UserIpMap |
|
19 | from kallithea.model.db import Setting, User, UserIpMap | |
20 | from kallithea.model.scm import ScmModel |
|
20 | from kallithea.model.scm import ScmModel | |
21 | from kallithea.tests.base import invalidate_all_caches, TEST_USER_REGULAR_LOGIN, TESTS_TMP_PATH, \ |
|
21 | from kallithea.tests.base import invalidate_all_caches, TEST_USER_REGULAR_LOGIN, TESTS_TMP_PATH, \ | |
22 | TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS |
|
22 | TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS | |
23 | import kallithea.tests.base # FIXME: needed for setting testapp instance!!! |
|
23 | import kallithea.tests.base # FIXME: needed for setting testapp instance!!! | |
24 |
|
24 | |||
25 | from tg.util.webtest import test_context |
|
25 | from tg.util.webtest import test_context | |
26 |
|
26 | |||
27 |
|
27 | |||
28 | def pytest_configure(): |
|
28 | def pytest_configure(): | |
29 | os.environ['TZ'] = 'UTC' |
|
29 | os.environ['TZ'] = 'UTC' | |
30 | if not kallithea.is_windows: |
|
30 | if not kallithea.is_windows: | |
31 | time.tzset() # only available on Unix |
|
31 | time.tzset() # only available on Unix | |
32 |
|
32 | |||
33 | path = os.getcwd() |
|
33 | path = os.getcwd() | |
34 | sys.path.insert(0, path) |
|
34 | sys.path.insert(0, path) | |
35 | pkg_resources.working_set.add_entry(path) |
|
35 | pkg_resources.working_set.add_entry(path) | |
36 |
|
36 | |||
37 | # Disable INFO logging of test database creation, restore with NOTSET |
|
37 | # Disable INFO logging of test database creation, restore with NOTSET | |
38 | logging.disable(logging.INFO) |
|
38 | logging.disable(logging.INFO) | |
39 |
|
39 | |||
40 | ini_settings = { |
|
40 | ini_settings = { | |
41 | '[server:main]': { |
|
41 | '[server:main]': { | |
42 | 'port': '4999', |
|
42 | 'port': '4999', | |
43 | }, |
|
43 | }, | |
44 | '[app:main]': { |
|
44 | '[app:main]': { | |
45 | 'app_instance_uuid': 'test', |
|
45 | 'app_instance_uuid': 'test', | |
46 | 'show_revision_number': 'true', |
|
46 | 'show_revision_number': 'true', | |
47 | 'beaker.cache.sql_cache_short.expire': '1', |
|
47 | 'beaker.cache.sql_cache_short.expire': '1', | |
48 | 'beaker.session.secret': '{74e0cd75-b339-478b-b129-07dd221def1f}', |
|
48 | 'beaker.session.secret': '{74e0cd75-b339-478b-b129-07dd221def1f}', | |
|
49 | #'i18n.lang': '', | |||
49 | }, |
|
50 | }, | |
50 | '[handler_console]': { |
|
51 | '[handler_console]': { | |
51 | 'formatter': 'color_formatter', |
|
52 | 'formatter': 'color_formatter', | |
52 | }, |
|
53 | }, | |
53 | # The 'handler_console_sql' block is very similar to the one in |
|
54 | # The 'handler_console_sql' block is very similar to the one in | |
54 | # development.ini, but without the explicit 'level=DEBUG' setting: |
|
55 | # development.ini, but without the explicit 'level=DEBUG' setting: | |
55 | # it causes duplicate sqlalchemy debug logs, one through |
|
56 | # it causes duplicate sqlalchemy debug logs, one through | |
56 | # handler_console_sql and another through another path. |
|
57 | # handler_console_sql and another through another path. | |
57 | '[handler_console_sql]': { |
|
58 | '[handler_console_sql]': { | |
58 | 'formatter': 'color_formatter_sql', |
|
59 | 'formatter': 'color_formatter_sql', | |
59 | }, |
|
60 | }, | |
60 | } |
|
61 | } | |
61 | if os.environ.get('TEST_DB'): |
|
62 | if os.environ.get('TEST_DB'): | |
62 | ini_settings['[app:main]']['sqlalchemy.url'] = os.environ.get('TEST_DB') |
|
63 | ini_settings['[app:main]']['sqlalchemy.url'] = os.environ.get('TEST_DB') | |
63 |
|
64 | |||
64 | test_ini_file = os.path.join(TESTS_TMP_PATH, 'test.ini') |
|
65 | test_ini_file = os.path.join(TESTS_TMP_PATH, 'test.ini') | |
65 | inifile.create(test_ini_file, None, ini_settings) |
|
66 | inifile.create(test_ini_file, None, ini_settings) | |
66 |
|
67 | |||
67 | context = loadwsgi.loadcontext(loadwsgi.APP, 'config:%s' % test_ini_file) |
|
68 | context = loadwsgi.loadcontext(loadwsgi.APP, 'config:%s' % test_ini_file) | |
68 | from kallithea.tests.fixture import create_test_env, create_test_index |
|
69 | from kallithea.tests.fixture import create_test_env, create_test_index | |
69 |
|
70 | |||
70 | # set KALLITHEA_NO_TMP_PATH=1 to disable re-creating the database and test repos |
|
71 | # set KALLITHEA_NO_TMP_PATH=1 to disable re-creating the database and test repos | |
71 | if not int(os.environ.get('KALLITHEA_NO_TMP_PATH', 0)): |
|
72 | if not int(os.environ.get('KALLITHEA_NO_TMP_PATH', 0)): | |
72 | create_test_env(TESTS_TMP_PATH, context.config()) |
|
73 | create_test_env(TESTS_TMP_PATH, context.config()) | |
73 |
|
74 | |||
74 | # set KALLITHEA_WHOOSH_TEST_DISABLE=1 to disable whoosh index during tests |
|
75 | # set KALLITHEA_WHOOSH_TEST_DISABLE=1 to disable whoosh index during tests | |
75 | if not int(os.environ.get('KALLITHEA_WHOOSH_TEST_DISABLE', 0)): |
|
76 | if not int(os.environ.get('KALLITHEA_WHOOSH_TEST_DISABLE', 0)): | |
76 | create_test_index(TESTS_TMP_PATH, context.config(), True) |
|
77 | create_test_index(TESTS_TMP_PATH, context.config(), True) | |
77 |
|
78 | |||
78 | kallithea.tests.base.testapp = context.create() |
|
79 | kallithea.tests.base.testapp = context.create() | |
79 | # do initial repo scan |
|
80 | # do initial repo scan | |
80 | repo2db_mapper(ScmModel().repo_scan(TESTS_TMP_PATH)) |
|
81 | repo2db_mapper(ScmModel().repo_scan(TESTS_TMP_PATH)) | |
81 |
|
82 | |||
82 | logging.disable(logging.NOTSET) |
|
83 | logging.disable(logging.NOTSET) | |
83 |
|
84 | |||
84 | kallithea.tests.base.url = URLGenerator(RootController().mapper, {'HTTP_HOST': 'example.com'}) |
|
85 | kallithea.tests.base.url = URLGenerator(RootController().mapper, {'HTTP_HOST': 'example.com'}) | |
85 |
|
86 | |||
86 | # set fixed language for form messages, regardless of environment settings |
|
87 | # set fixed language for form messages, regardless of environment settings | |
87 | formencode.api.set_stdtranslation(languages=[]) |
|
88 | formencode.api.set_stdtranslation(languages=[]) | |
88 |
|
89 | |||
89 |
|
90 | |||
90 | @pytest.fixture |
|
91 | @pytest.fixture | |
91 | def create_test_user(): |
|
92 | def create_test_user(): | |
92 | """Provide users that automatically disappear after test is over.""" |
|
93 | """Provide users that automatically disappear after test is over.""" | |
93 | test_user_ids = [] |
|
94 | test_user_ids = [] | |
94 |
|
95 | |||
95 | def _create_test_user(user_form): |
|
96 | def _create_test_user(user_form): | |
96 | user = UserModel().create(user_form) |
|
97 | user = UserModel().create(user_form) | |
97 | test_user_ids.append(user.user_id) |
|
98 | test_user_ids.append(user.user_id) | |
98 | return user |
|
99 | return user | |
99 | yield _create_test_user |
|
100 | yield _create_test_user | |
100 | for user_id in test_user_ids: |
|
101 | for user_id in test_user_ids: | |
101 | UserModel().delete(user_id) |
|
102 | UserModel().delete(user_id) | |
102 | Session().commit() |
|
103 | Session().commit() | |
103 |
|
104 | |||
104 |
|
105 | |||
105 | def _set_settings(*kvtseq): |
|
106 | def _set_settings(*kvtseq): | |
106 | session = Session() |
|
107 | session = Session() | |
107 | for kvt in kvtseq: |
|
108 | for kvt in kvtseq: | |
108 | assert len(kvt) in (2, 3) |
|
109 | assert len(kvt) in (2, 3) | |
109 | k = kvt[0] |
|
110 | k = kvt[0] | |
110 | v = kvt[1] |
|
111 | v = kvt[1] | |
111 | t = kvt[2] if len(kvt) == 3 else 'unicode' |
|
112 | t = kvt[2] if len(kvt) == 3 else 'unicode' | |
112 | Setting.create_or_update(k, v, t) |
|
113 | Setting.create_or_update(k, v, t) | |
113 | session.commit() |
|
114 | session.commit() | |
114 |
|
115 | |||
115 |
|
116 | |||
116 | @pytest.fixture |
|
117 | @pytest.fixture | |
117 | def set_test_settings(): |
|
118 | def set_test_settings(): | |
118 | """Restore settings after test is over.""" |
|
119 | """Restore settings after test is over.""" | |
119 | # Save settings. |
|
120 | # Save settings. | |
120 | settings_snapshot = [ |
|
121 | settings_snapshot = [ | |
121 | (s.app_settings_name, s.app_settings_value, s.app_settings_type) |
|
122 | (s.app_settings_name, s.app_settings_value, s.app_settings_type) | |
122 | for s in Setting.query().all()] |
|
123 | for s in Setting.query().all()] | |
123 | yield _set_settings |
|
124 | yield _set_settings | |
124 | # Restore settings. |
|
125 | # Restore settings. | |
125 | session = Session() |
|
126 | session = Session() | |
126 | keys = frozenset(k for (k, v, t) in settings_snapshot) |
|
127 | keys = frozenset(k for (k, v, t) in settings_snapshot) | |
127 | for s in Setting.query().all(): |
|
128 | for s in Setting.query().all(): | |
128 | if s.app_settings_name not in keys: |
|
129 | if s.app_settings_name not in keys: | |
129 | session.delete(s) |
|
130 | session.delete(s) | |
130 | for k, v, t in settings_snapshot: |
|
131 | for k, v, t in settings_snapshot: | |
131 | if t == 'list' and hasattr(v, '__iter__'): |
|
132 | if t == 'list' and hasattr(v, '__iter__'): | |
132 | v = ','.join(v) # Quirk: must format list value manually. |
|
133 | v = ','.join(v) # Quirk: must format list value manually. | |
133 | Setting.create_or_update(k, v, t) |
|
134 | Setting.create_or_update(k, v, t) | |
134 | session.commit() |
|
135 | session.commit() | |
135 |
|
136 | |||
136 |
|
137 | |||
137 | @pytest.fixture |
|
138 | @pytest.fixture | |
138 | def auto_clear_ip_permissions(): |
|
139 | def auto_clear_ip_permissions(): | |
139 | """Fixture that provides nothing but clearing IP permissions upon test |
|
140 | """Fixture that provides nothing but clearing IP permissions upon test | |
140 | exit. This clearing is needed to avoid other test failing to make fake http |
|
141 | exit. This clearing is needed to avoid other test failing to make fake http | |
141 | accesses.""" |
|
142 | accesses.""" | |
142 | yield |
|
143 | yield | |
143 | # cleanup |
|
144 | # cleanup | |
144 | user_model = UserModel() |
|
145 | user_model = UserModel() | |
145 |
|
146 | |||
146 | user_ids = [] |
|
147 | user_ids = [] | |
147 | user_ids.append(User.get_default_user().user_id) |
|
148 | user_ids.append(User.get_default_user().user_id) | |
148 | user_ids.append(User.get_by_username(TEST_USER_REGULAR_LOGIN).user_id) |
|
149 | user_ids.append(User.get_by_username(TEST_USER_REGULAR_LOGIN).user_id) | |
149 |
|
150 | |||
150 | for user_id in user_ids: |
|
151 | for user_id in user_ids: | |
151 | for ip in UserIpMap.query().filter(UserIpMap.user_id == user_id): |
|
152 | for ip in UserIpMap.query().filter(UserIpMap.user_id == user_id): | |
152 | user_model.delete_extra_ip(user_id, ip.ip_id) |
|
153 | user_model.delete_extra_ip(user_id, ip.ip_id) | |
153 |
|
154 | |||
154 | # IP permissions are cached, need to invalidate this cache explicitly |
|
155 | # IP permissions are cached, need to invalidate this cache explicitly | |
155 | invalidate_all_caches() |
|
156 | invalidate_all_caches() | |
156 |
|
157 | |||
157 |
|
158 | |||
158 | @pytest.fixture |
|
159 | @pytest.fixture | |
159 | def test_context_fixture(app_fixture): |
|
160 | def test_context_fixture(app_fixture): | |
160 | """ |
|
161 | """ | |
161 | Encompass the entire test using this fixture in a test_context, |
|
162 | Encompass the entire test using this fixture in a test_context, | |
162 | making sure that certain functionality still works even if no call to |
|
163 | making sure that certain functionality still works even if no call to | |
163 | self.app.get/post has been made. |
|
164 | self.app.get/post has been made. | |
164 | The typical error message indicating you need a test_context is: |
|
165 | The typical error message indicating you need a test_context is: | |
165 | TypeError: No object (name: context) has been registered for this thread |
|
166 | TypeError: No object (name: context) has been registered for this thread | |
166 |
|
167 | |||
167 | The standard way to fix this is simply using the test_context context |
|
168 | The standard way to fix this is simply using the test_context context | |
168 | manager directly inside your test: |
|
169 | manager directly inside your test: | |
169 | with test_context(self.app): |
|
170 | with test_context(self.app): | |
170 | <actions> |
|
171 | <actions> | |
171 | but if test setup code (xUnit-style or pytest fixtures) also needs to be |
|
172 | but if test setup code (xUnit-style or pytest fixtures) also needs to be | |
172 | executed inside the test context, that method is not possible. |
|
173 | executed inside the test context, that method is not possible. | |
173 | Even if there is no such setup code, the fixture may reduce code complexity |
|
174 | Even if there is no such setup code, the fixture may reduce code complexity | |
174 | if the entire test needs to run inside a test context. |
|
175 | if the entire test needs to run inside a test context. | |
175 |
|
176 | |||
176 | To apply this fixture (like any other fixture) to all test methods of a |
|
177 | To apply this fixture (like any other fixture) to all test methods of a | |
177 | class, use the following class decorator: |
|
178 | class, use the following class decorator: | |
178 | @pytest.mark.usefixtures("test_context_fixture") |
|
179 | @pytest.mark.usefixtures("test_context_fixture") | |
179 | class TestFoo(TestController): |
|
180 | class TestFoo(TestController): | |
180 | ... |
|
181 | ... | |
181 | """ |
|
182 | """ | |
182 | with test_context(app_fixture): |
|
183 | with test_context(app_fixture): | |
183 | yield |
|
184 | yield | |
184 |
|
185 | |||
185 |
|
186 | |||
186 | class MyWSGIServer(WSGIServer): |
|
187 | class MyWSGIServer(WSGIServer): | |
187 | def repo_url(self, repo_name, username=TEST_USER_ADMIN_LOGIN, password=TEST_USER_ADMIN_PASS): |
|
188 | def repo_url(self, repo_name, username=TEST_USER_ADMIN_LOGIN, password=TEST_USER_ADMIN_PASS): | |
188 | """Return URL to repo on this web server.""" |
|
189 | """Return URL to repo on this web server.""" | |
189 | host, port = self.server_address |
|
190 | host, port = self.server_address | |
190 | proto = 'http' if self._server.ssl_context is None else 'https' |
|
191 | proto = 'http' if self._server.ssl_context is None else 'https' | |
191 | auth = '' |
|
192 | auth = '' | |
192 | if username is not None: |
|
193 | if username is not None: | |
193 | auth = username |
|
194 | auth = username | |
194 | if password is not None: |
|
195 | if password is not None: | |
195 | auth += ':' + password |
|
196 | auth += ':' + password | |
196 | if auth: |
|
197 | if auth: | |
197 | auth += '@' |
|
198 | auth += '@' | |
198 | return '%s://%s%s:%s/%s' % (proto, auth, host, port, repo_name) |
|
199 | return '%s://%s%s:%s/%s' % (proto, auth, host, port, repo_name) | |
199 |
|
200 | |||
200 |
|
201 | |||
201 | @pytest.yield_fixture(scope="session") |
|
202 | @pytest.yield_fixture(scope="session") | |
202 | def webserver(): |
|
203 | def webserver(): | |
203 | """Start web server while tests are running. |
|
204 | """Start web server while tests are running. | |
204 | Useful for debugging and necessary for vcs operation tests.""" |
|
205 | Useful for debugging and necessary for vcs operation tests.""" | |
205 | server = MyWSGIServer(application=kallithea.tests.base.testapp) |
|
206 | server = MyWSGIServer(application=kallithea.tests.base.testapp) | |
206 | server.start() |
|
207 | server.start() | |
207 |
|
208 | |||
208 | yield server |
|
209 | yield server | |
209 |
|
210 | |||
210 | server.stop() |
|
211 | server.stop() |
@@ -1,74 +1,72 b'' | |||||
1 | #!/usr/bin/env python2 |
|
1 | #!/usr/bin/env python2 | |
2 | """ |
|
2 | """ | |
3 | Based on kallithea/lib/paster_commands/template.ini.mako, generate |
|
3 | Based on kallithea/lib/paster_commands/template.ini.mako, generate development.ini | |
4 | development.ini |
|
|||
5 | kallithea/tests/test.ini |
|
|||
6 | """ |
|
4 | """ | |
7 |
|
5 | |||
8 | import re |
|
6 | import re | |
9 |
|
7 | |||
10 | from kallithea.lib import inifile |
|
8 | from kallithea.lib import inifile | |
11 |
|
9 | |||
12 | # files to be generated from the mako template |
|
10 | # files to be generated from the mako template | |
13 | ini_files = [ |
|
11 | ini_files = [ | |
14 | ('development.ini', |
|
12 | ('development.ini', | |
15 | { |
|
13 | { | |
16 | '[server:main]': { |
|
14 | '[server:main]': { | |
17 | 'host': '0.0.0.0', |
|
15 | 'host': '0.0.0.0', | |
18 | }, |
|
16 | }, | |
19 | '[app:main]': { |
|
17 | '[app:main]': { | |
20 | 'debug': 'true', |
|
18 | 'debug': 'true', | |
21 | 'app_instance_uuid': 'development-not-secret', |
|
19 | 'app_instance_uuid': 'development-not-secret', | |
22 | 'beaker.session.secret': 'development-not-secret', |
|
20 | 'beaker.session.secret': 'development-not-secret', | |
23 | }, |
|
21 | }, | |
24 | '[handler_console]': { |
|
22 | '[handler_console]': { | |
25 | 'formatter': 'color_formatter', |
|
23 | 'formatter': 'color_formatter', | |
26 | }, |
|
24 | }, | |
27 | '[handler_console_sql]': { |
|
25 | '[handler_console_sql]': { | |
28 | 'formatter': 'color_formatter_sql', |
|
26 | 'formatter': 'color_formatter_sql', | |
29 | }, |
|
27 | }, | |
30 | '[logger_routes]': { |
|
28 | '[logger_routes]': { | |
31 | 'level': 'DEBUG', |
|
29 | 'level': 'DEBUG', | |
32 | }, |
|
30 | }, | |
33 | '[logger_beaker]': { |
|
31 | '[logger_beaker]': { | |
34 | 'level': 'DEBUG', |
|
32 | 'level': 'DEBUG', | |
35 | }, |
|
33 | }, | |
36 | '[logger_templates]': { |
|
34 | '[logger_templates]': { | |
37 | 'level': 'INFO', |
|
35 | 'level': 'INFO', | |
38 | }, |
|
36 | }, | |
39 | '[logger_kallithea]': { |
|
37 | '[logger_kallithea]': { | |
40 | 'level': 'DEBUG', |
|
38 | 'level': 'DEBUG', | |
41 | }, |
|
39 | }, | |
42 | '[logger_tg]': { |
|
40 | '[logger_tg]': { | |
43 | 'level': 'DEBUG', |
|
41 | 'level': 'DEBUG', | |
44 | }, |
|
42 | }, | |
45 | '[logger_gearbox]': { |
|
43 | '[logger_gearbox]': { | |
46 | 'level': 'DEBUG', |
|
44 | 'level': 'DEBUG', | |
47 | }, |
|
45 | }, | |
48 | '[logger_whoosh_indexer]': { |
|
46 | '[logger_whoosh_indexer]': { | |
49 | 'level': 'DEBUG', |
|
47 | 'level': 'DEBUG', | |
50 | }, |
|
48 | }, | |
51 | }, |
|
49 | }, | |
52 | ), |
|
50 | ), | |
53 | ] |
|
51 | ] | |
54 |
|
52 | |||
55 |
|
53 | |||
56 | def main(): |
|
54 | def main(): | |
57 | # make sure all mako lines starting with '#' (the '##' comments) are marked up as <text> |
|
55 | # make sure all mako lines starting with '#' (the '##' comments) are marked up as <text> | |
58 | makofile = inifile.template_file |
|
56 | makofile = inifile.template_file | |
59 | print 'reading:', makofile |
|
57 | print 'reading:', makofile | |
60 | mako_org = open(makofile).read() |
|
58 | mako_org = open(makofile).read() | |
61 | mako_no_text_markup = re.sub(r'</?%text>', '', mako_org) |
|
59 | mako_no_text_markup = re.sub(r'</?%text>', '', mako_org) | |
62 | mako_marked_up = re.sub(r'\n(##.*)', r'\n<%text>\1</%text>', mako_no_text_markup, flags=re.MULTILINE) |
|
60 | mako_marked_up = re.sub(r'\n(##.*)', r'\n<%text>\1</%text>', mako_no_text_markup, flags=re.MULTILINE) | |
63 | if mako_marked_up != mako_org: |
|
61 | if mako_marked_up != mako_org: | |
64 | print 'writing:', makofile |
|
62 | print 'writing:', makofile | |
65 | open(makofile, 'w').write(mako_marked_up) |
|
63 | open(makofile, 'w').write(mako_marked_up) | |
66 |
|
64 | |||
67 | # create ini files |
|
65 | # create ini files | |
68 | for fn, settings in ini_files: |
|
66 | for fn, settings in ini_files: | |
69 | print 'updating:', fn |
|
67 | print 'updating:', fn | |
70 | inifile.create(fn, None, settings) |
|
68 | inifile.create(fn, None, settings) | |
71 |
|
69 | |||
72 |
|
70 | |||
73 | if __name__ == '__main__': |
|
71 | if __name__ == '__main__': | |
74 | main() |
|
72 | main() |
General Comments 0
You need to be logged in to leave comments.
Login now