Merge lp:~lifeless/launchpad/databasefixture into lp:launchpad
- databasefixture
- Merge into devel
Proposed by
Robert Collins
Status: | Merged |
---|---|
Approved by: | Robert Collins |
Approved revision: | no longer in the source branch. |
Merged at revision: | 11734 |
Proposed branch: | lp:~lifeless/launchpad/databasefixture |
Merge into: | lp:launchpad |
Diff against target: |
1478 lines (+247/-460) 21 files modified
lib/canonical/config/tests/test_database_config.py (+10/-6) lib/canonical/database/ftests/test_postgresql.py (+5/-3) lib/canonical/database/ftests/test_sqlbaseconnect.txt (+2/-2) lib/canonical/ftests/pgsql.py (+11/-30) lib/canonical/ftests/test_pgsql.py (+60/-63) lib/canonical/launchpad/doc/canonical-config.txt (+6/-4) lib/canonical/launchpad/doc/old-testing.txt (+19/-127) lib/canonical/launchpad/doc/security-proxies.txt (+0/-8) lib/canonical/launchpad/ftests/harness.py (+0/-84) lib/canonical/launchpad/pagetests/standalone/xx-dbpolicy.txt (+5/-3) lib/canonical/launchpad/tests/test_sampledata.py (+2/-6) lib/canonical/launchpad/webapp/ftests/test_adapter.txt (+6/-2) lib/canonical/lp/ftests/test_zopeless.py (+14/-9) lib/canonical/testing/ftests/test_layers.py (+26/-21) lib/canonical/testing/layers.py (+53/-55) lib/lp/codehosting/tests/test_acceptance.py (+20/-21) lib/lp/soyuz/doc/sampledata-setup.txt (+2/-2) lib/lp/soyuz/scripts/tests/test_buildd_cronscripts.py (+2/-3) lib/lp/translations/doc/fix_translation_credits.txt (+2/-2) lib/lp/translations/doc/message-sharing-merge-script.txt (+2/-2) lib/lp/translations/doc/request_country.txt (+0/-7) |
To merge this branch: | bzr merge lp:~lifeless/launchpad/databasefixture |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jonathan Lange (community) | Approve | ||
Review via email: mp+38643@code.launchpad.net |
Commit message
Make database test support capable of running with unique db test names.
Description of the change
This makes the database test support layer ready for parallel testing - we can run with unique database names. Not supported yet is having the config system unique for a test process (I'd make it more granular than that, but zcml is so global that I don't think thats feasible until we reengineer that entire stack.
Some things were no longer used, so I deleted them. Most things were trivial.
We won't see any test DB leaks at this point, but I'm worried about the possibility when we turn this on... still thats a problem for another day.
To post a comment you must log in.
Revision history for this message
Jonathan Lange (jml) wrote : | # |
Revision history for this message
Jonathan Lange (jml) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/canonical/config/tests/test_database_config.py' |
2 | --- lib/canonical/config/tests/test_database_config.py 2010-01-13 20:06:09 +0000 |
3 | +++ lib/canonical/config/tests/test_database_config.py 2010-10-17 19:12:52 +0000 |
4 | @@ -3,17 +3,20 @@ |
5 | |
6 | __metaclass__ = type |
7 | |
8 | -from lp.testing import TestCase |
9 | - |
10 | from canonical.config import config, dbconfig |
11 | - |
12 | from canonical.launchpad.readonly import read_only_file_exists |
13 | from canonical.launchpad.tests.readonly import ( |
14 | - remove_read_only_file, touch_read_only_file) |
15 | + remove_read_only_file, |
16 | + touch_read_only_file, |
17 | + ) |
18 | +from canonical.testing.layers import DatabaseLayer |
19 | +from lp.testing import TestCase |
20 | |
21 | |
22 | class TestDatabaseConfig(TestCase): |
23 | |
24 | + layer = DatabaseLayer |
25 | + |
26 | def test_overlay(self): |
27 | # The dbconfig option overlays the database configurations of a |
28 | # chosen config section over the base section. |
29 | @@ -25,11 +28,12 @@ |
30 | self.assertEquals('librarian', config.librarian.dbuser) |
31 | |
32 | dbconfig.setConfigSection('librarian') |
33 | - self.assertEquals('dbname=launchpad_ftest', dbconfig.rw_main_master) |
34 | + expected_db = 'dbname=%s' % DatabaseLayer._db_fixture.dbname |
35 | + self.assertEquals(expected_db, dbconfig.rw_main_master) |
36 | self.assertEquals('librarian', dbconfig.dbuser) |
37 | |
38 | dbconfig.setConfigSection('launchpad') |
39 | - self.assertEquals('dbname=launchpad_ftest', dbconfig.rw_main_master) |
40 | + self.assertEquals(expected_db, dbconfig.rw_main_master) |
41 | self.assertEquals('launchpad_main', dbconfig.dbuser) |
42 | |
43 | def test_required_values(self): |
44 | |
45 | === modified file 'lib/canonical/database/ftests/test_postgresql.py' |
46 | --- lib/canonical/database/ftests/test_postgresql.py 2010-07-14 14:11:15 +0000 |
47 | +++ lib/canonical/database/ftests/test_postgresql.py 2010-10-17 19:12:52 +0000 |
48 | @@ -10,8 +10,9 @@ |
49 | def setUp(test): |
50 | |
51 | # Build a fresh, empty database and connect |
52 | - PgTestSetup().setUp() |
53 | - con = PgTestSetup().connect() |
54 | + test._db_fixture = PgTestSetup() |
55 | + test._db_fixture.setUp() |
56 | + con = test._db_fixture.connect() |
57 | |
58 | # Create a test schema demonstrating the edge cases |
59 | cur = con.cursor() |
60 | @@ -53,8 +54,9 @@ |
61 | test.globs['cur'] = cur |
62 | |
63 | def tearDown(test): |
64 | - PgTestSetup().tearDown() |
65 | test.globs['con'].close() |
66 | + test._db_fixture.tearDown() |
67 | + del test._db_fixture |
68 | |
69 | def test_suite(): |
70 | suite = DocTestSuite( |
71 | |
72 | === modified file 'lib/canonical/database/ftests/test_sqlbaseconnect.txt' |
73 | --- lib/canonical/database/ftests/test_sqlbaseconnect.txt 2009-04-17 10:32:16 +0000 |
74 | +++ lib/canonical/database/ftests/test_sqlbaseconnect.txt 2010-10-17 19:12:52 +0000 |
75 | @@ -19,7 +19,7 @@ |
76 | Specifying the user connects as that user. |
77 | |
78 | >>> do_connect(user=config.launchpad_session.dbuser) |
79 | - Connected as session to launchpad_ftest in read committed isolation. |
80 | + Connected as session to ... in read committed isolation. |
81 | |
82 | Specifying the database name connects to that database. |
83 | |
84 | @@ -31,5 +31,5 @@ |
85 | >>> do_connect( |
86 | ... user=config.launchpad.dbuser, |
87 | ... isolation=ISOLATION_LEVEL_SERIALIZABLE) |
88 | - Connected as launchpad_main to launchpad_ftest in serializable isolation. |
89 | + Connected as launchpad_main to ... in serializable isolation. |
90 | |
91 | |
92 | === modified file 'lib/canonical/ftests/pgsql.py' |
93 | --- lib/canonical/ftests/pgsql.py 2009-10-09 04:05:34 +0000 |
94 | +++ lib/canonical/ftests/pgsql.py 2010-10-17 19:12:52 +0000 |
95 | @@ -7,7 +7,7 @@ |
96 | |
97 | __metaclass__ = type |
98 | |
99 | -import unittest |
100 | +import os |
101 | import time |
102 | |
103 | import psycopg2 |
104 | @@ -119,7 +119,6 @@ |
105 | |
106 | _org_connect = None |
107 | def fake_connect(*args, **kw): |
108 | - global _org_connect |
109 | return ConnectionWrapper(_org_connect(*args, **kw)) |
110 | |
111 | def installFakeConnect(): |
112 | @@ -136,9 +135,13 @@ |
113 | |
114 | |
115 | class PgTestSetup: |
116 | + |
117 | connections = [] # Shared |
118 | + # Use a dynamically generated dbname: |
119 | + dynamic = object() |
120 | |
121 | template = 'template1' |
122 | + # Needs to match configs/testrunner*/*: |
123 | dbname = 'launchpad_ftest' |
124 | dbuser = None |
125 | host = None |
126 | @@ -165,8 +168,13 @@ |
127 | ''' |
128 | if template is not None: |
129 | self.template = template |
130 | - if dbname is not None: |
131 | + if dbname is PgTestSetup.dynamic: |
132 | + self.dbname = self.__class__.dbname + "_" + str(os.getpid()) |
133 | + elif dbname is not None: |
134 | self.dbname = dbname |
135 | + else: |
136 | + # Fallback to the class name. |
137 | + self.dbname = self.__class__.dbname |
138 | if dbuser is not None: |
139 | self.dbuser = dbuser |
140 | if host is not None: |
141 | @@ -331,30 +339,3 @@ |
142 | as database changes made from a subprocess. |
143 | """ |
144 | PgTestSetup._reset_db = True |
145 | - |
146 | - |
147 | -class PgTestCase(unittest.TestCase): |
148 | - dbname = None |
149 | - dbuser = None |
150 | - host = None |
151 | - port = None |
152 | - template = None |
153 | - def setUp(self): |
154 | - pg_test_setup = PgTestSetup( |
155 | - self.template, self.dbname, self.dbuser, self.host, self.port |
156 | - ) |
157 | - pg_test_setup.setUp() |
158 | - self.dbname = pg_test_setup.dbname |
159 | - self.dbuser = pg_test_setup.dbuser |
160 | - assert self.dbname, 'self.dbname is not set.' |
161 | - |
162 | - def tearDown(self): |
163 | - PgTestSetup( |
164 | - self.template, self.dbname, self.dbuser, self.host, self.port |
165 | - ).tearDown() |
166 | - |
167 | - def connect(self): |
168 | - return PgTestSetup( |
169 | - self.template, self.dbname, self.dbuser, self.host, self.port |
170 | - ).connect() |
171 | - |
172 | |
173 | === modified file 'lib/canonical/ftests/test_pgsql.py' |
174 | --- lib/canonical/ftests/test_pgsql.py 2009-06-25 05:30:52 +0000 |
175 | +++ lib/canonical/ftests/test_pgsql.py 2010-10-17 19:12:52 +0000 |
176 | @@ -1,77 +1,84 @@ |
177 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
178 | +# Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
179 | # GNU Affero General Public License version 3 (see the file LICENSE). |
180 | |
181 | -import unittest |
182 | -from canonical.ftests.pgsql import PgTestCase, PgTestSetup, ConnectionWrapper |
183 | - |
184 | - |
185 | -class TestPgTestCase(PgTestCase): |
186 | - |
187 | - def testRollback(self): |
188 | - # This test creates a table. We run the same test twice, |
189 | - # which will fail if database changes are not rolled back |
190 | - con = self.connect() |
191 | - cur = con.cursor() |
192 | - cur.execute('CREATE TABLE foo (x int)') |
193 | - cur.execute('INSERT INTO foo VALUES (1)') |
194 | - cur.execute('SELECT x FROM foo') |
195 | - res = list(cur.fetchall()) |
196 | - self.failUnless(len(res) == 1) |
197 | - self.failUnless(res[0][0] == 1) |
198 | - con.commit() |
199 | - |
200 | - testRollback2 = testRollback |
201 | - |
202 | -class TestOptimization(unittest.TestCase): |
203 | +import os |
204 | + |
205 | +import testtools |
206 | + |
207 | +from canonical.ftests.pgsql import ( |
208 | + ConnectionWrapper, |
209 | + PgTestSetup, |
210 | + ) |
211 | + |
212 | + |
213 | +class TestPgTestSetup(testtools.TestCase): |
214 | + |
215 | + def test_db_naming(self): |
216 | + fixture = PgTestSetup(dbname=PgTestSetup.dynamic) |
217 | + expected_name = "%s_%s" % (PgTestSetup.dbname, os.getpid()) |
218 | + self.assertEqual(expected_name, fixture.dbname) |
219 | + fixture.setUp() |
220 | + self.addCleanup(fixture.dropDb) |
221 | + self.addCleanup(fixture.tearDown) |
222 | + cur = fixture.connect().cursor() |
223 | + cur.execute('SELECT current_database()') |
224 | + where = cur.fetchone()[0] |
225 | + self.assertEqual(expected_name, where) |
226 | + |
227 | def testOptimization(self): |
228 | # Test to ensure that the database is destroyed only when necessary |
229 | |
230 | # Make a change to a database |
231 | - PgTestSetup().setUp() |
232 | + fixture = PgTestSetup() |
233 | + fixture.setUp() |
234 | try: |
235 | - con = PgTestSetup().connect() |
236 | + con = fixture.connect() |
237 | cur = con.cursor() |
238 | cur.execute('CREATE TABLE foo (x int)') |
239 | con.commit() |
240 | # Fake it so the harness doesn't know a change has been made |
241 | ConnectionWrapper.committed = False |
242 | finally: |
243 | - PgTestSetup().tearDown() |
244 | + fixture.tearDown() |
245 | |
246 | - # Now check to ensure that the table we just created is still there |
247 | - PgTestSetup().setUp() |
248 | + # Now check to ensure that the table we just created is still there if |
249 | + # we reuse the fixture. |
250 | + fixture.setUp() |
251 | try: |
252 | - con = PgTestSetup().connect() |
253 | + con = fixture.connect() |
254 | cur = con.cursor() |
255 | # This tests that the table still exists, as well as modifying the |
256 | # db |
257 | cur.execute('INSERT INTO foo VALUES (1)') |
258 | con.commit() |
259 | finally: |
260 | - PgTestSetup().tearDown() |
261 | + fixture.tearDown() |
262 | |
263 | - # Now ensure that the table is gone |
264 | - PgTestSetup().setUp() |
265 | + # Now ensure that the table is gone - the commit must have been rolled |
266 | + # back. |
267 | + fixture.setUp() |
268 | try: |
269 | - con = PgTestSetup().connect() |
270 | + con = fixture.connect() |
271 | cur = con.cursor() |
272 | cur.execute('CREATE TABLE foo (x int)') |
273 | con.commit() |
274 | ConnectionWrapper.committed = False # Leave the table |
275 | finally: |
276 | - PgTestSetup().tearDown() |
277 | + fixture.tearDown() |
278 | |
279 | - # The database should *always* be recreated if the template |
280 | - # changes. |
281 | - PgTestSetup._last_db = ('whatever', 'launchpad_ftest') |
282 | - PgTestSetup().setUp() |
283 | + # The database should *always* be recreated if a new template had been |
284 | + # chosen. |
285 | + PgTestSetup._last_db = ('different-template', fixture.dbname) |
286 | + fixture.setUp() |
287 | try: |
288 | - con = PgTestSetup().connect() |
289 | + con = fixture.connect() |
290 | cur = con.cursor() |
291 | + # If this fails, TABLE foo still existed and the DB wasn't rebuilt |
292 | + # correctly. |
293 | cur.execute('CREATE TABLE foo (x int)') |
294 | con.commit() |
295 | finally: |
296 | - PgTestSetup().tearDown() |
297 | + fixture.tearDown() |
298 | |
299 | def test_sequences(self): |
300 | # Sequences may be affected by connections even if the connection |
301 | @@ -80,9 +87,10 @@ |
302 | # the sequences. |
303 | |
304 | # Setup a table that uses a sequence |
305 | - PgTestSetup().setUp() |
306 | + fixture = PgTestSetup() |
307 | + fixture.setUp() |
308 | try: |
309 | - con = PgTestSetup().connect() |
310 | + con = fixture.connect() |
311 | cur = con.cursor() |
312 | cur.execute('CREATE TABLE foo (x serial, y integer)') |
313 | con.commit() |
314 | @@ -90,15 +98,15 @@ |
315 | # Fake it so the harness doesn't know a change has been made |
316 | ConnectionWrapper.committed = False |
317 | finally: |
318 | - PgTestSetup().tearDown() |
319 | + fixture.tearDown() |
320 | |
321 | sequence_values = [] |
322 | # Insert a row into it and roll back the changes. Each time, we |
323 | # should end up with the same sequence value |
324 | for i in range(3): |
325 | - PgTestSetup().setUp() |
326 | + fixture.setUp() |
327 | try: |
328 | - con = PgTestSetup().connect() |
329 | + con = fixture.connect() |
330 | cur = con.cursor() |
331 | cur.execute('INSERT INTO foo (y) VALUES (1)') |
332 | cur.execute("SELECT currval('foo_x_seq')") |
333 | @@ -106,7 +114,7 @@ |
334 | con.rollback() |
335 | con.close() |
336 | finally: |
337 | - PgTestSetup().tearDown() |
338 | + fixture.tearDown() |
339 | |
340 | # Fail if we got a diffent sequence value at some point |
341 | for v in sequence_values: |
342 | @@ -114,9 +122,9 @@ |
343 | |
344 | # Repeat the test, but this time with some data already in the |
345 | # table |
346 | - PgTestSetup().setUp() |
347 | + fixture.setUp() |
348 | try: |
349 | - con = PgTestSetup().connect() |
350 | + con = fixture.connect() |
351 | cur = con.cursor() |
352 | cur.execute('INSERT INTO foo (y) VALUES (1)') |
353 | con.commit() |
354 | @@ -124,15 +132,15 @@ |
355 | # Fake it so the harness doesn't know a change has been made |
356 | ConnectionWrapper.committed = False |
357 | finally: |
358 | - PgTestSetup().tearDown() |
359 | + fixture.tearDown() |
360 | |
361 | sequence_values = [] |
362 | # Insert a row into it and roll back the changes. Each time, we |
363 | # should end up with the same sequence value |
364 | for i in range(1,3): |
365 | - PgTestSetup().setUp() |
366 | + fixture.setUp() |
367 | try: |
368 | - con = PgTestSetup().connect() |
369 | + con = fixture.connect() |
370 | cur = con.cursor() |
371 | cur.execute('INSERT INTO foo (y) VALUES (1)') |
372 | cur.execute("SELECT currval('foo_x_seq')") |
373 | @@ -140,19 +148,8 @@ |
374 | con.rollback() |
375 | con.close() |
376 | finally: |
377 | - PgTestSetup().tearDown() |
378 | + fixture.tearDown() |
379 | |
380 | # Fail if we got a diffent sequence value at some point |
381 | for v in sequence_values: |
382 | self.failUnlessEqual(v, sequence_values[0]) |
383 | - |
384 | - |
385 | -def test_suite(): |
386 | - suite = unittest.TestSuite() |
387 | - suite.addTest(unittest.makeSuite(TestPgTestCase)) |
388 | - suite.addTest(unittest.makeSuite(TestOptimization)) |
389 | - return suite |
390 | - |
391 | -if __name__ == '__main__': |
392 | - unittest.main() |
393 | - |
394 | |
395 | === modified file 'lib/canonical/launchpad/doc/canonical-config.txt' |
396 | --- lib/canonical/launchpad/doc/canonical-config.txt 2010-01-05 19:09:58 +0000 |
397 | +++ lib/canonical/launchpad/doc/canonical-config.txt 2010-10-17 19:12:52 +0000 |
398 | @@ -14,8 +14,10 @@ |
399 | simple configuration). |
400 | |
401 | >>> from canonical.config import config |
402 | - >>> print config.database.rw_main_master |
403 | - dbname=launchpad_ftest |
404 | + >>> from canonical.testing.layers import DatabaseLayer |
405 | + >>> expected = 'dbname=%s' % DatabaseLayer._db_fixture.dbname |
406 | + >>> expected == config.database.rw_main_master |
407 | + True |
408 | >>> config.database.db_statement_timeout is None |
409 | True |
410 | >>> config.launchpad.dbuser |
411 | @@ -226,7 +228,7 @@ |
412 | # >>> canonical.config.config = config |
413 | # >>> config.filename |
414 | # '.../configs/testrunner/launchpad-lazr.conf' |
415 | -# >>> config.dbname |
416 | -# 'launchpad_ftest' |
417 | +# >>> config.dbname == DatabaseLayer._db_fixture.dbname |
418 | +# True |
419 | # >>> config._cache.testrunner |
420 | # <SectionValue for canonical 'testrunner'> |
421 | |
422 | === modified file 'lib/canonical/launchpad/doc/old-testing.txt' |
423 | --- lib/canonical/launchpad/doc/old-testing.txt 2010-10-03 20:23:37 +0000 |
424 | +++ lib/canonical/launchpad/doc/old-testing.txt 2010-10-17 19:12:52 +0000 |
425 | @@ -18,11 +18,6 @@ |
426 | zope, we should not be testing it with the full Z3 functional test |
427 | harness). |
428 | |
429 | -If you are wondering why we use `PgTestSetup().setUp()` and |
430 | -`PgTestSetup.tearDown()` instead of `pgtestsetup.setUp()` or |
431 | -`pgtestsetup.tearDown()`, it is because I'm mirroring the design used in |
432 | -Zope3's `FunctionalTestSetup`. |
433 | - |
434 | canonical.functional.FunctionalTestCase |
435 | --------------------------------------- |
436 | |
437 | @@ -42,11 +37,12 @@ |
438 | |
439 | The setup procedure builds us a fresh, empty database |
440 | |
441 | ->>> PgTestSetup().setUp() |
442 | +>>> fixture = PgTestSetup() |
443 | +>>> fixture.setUp() |
444 | |
445 | We can get connections to this database |
446 | |
447 | ->>> connection = PgTestSetup().connect() |
448 | +>>> connection = fixture.connect() |
449 | >>> cursor = connection.cursor() |
450 | >>> cursor.execute("""CREATE TABLE Beer ( |
451 | ... id serial PRIMARY KEY, name text, stamp timestamp without time zone |
452 | @@ -68,28 +64,29 @@ |
453 | When we have finished, we need to call the tearDown method which closes |
454 | all outstanding connections and destroys the database |
455 | |
456 | ->>> PgTestSetup().tearDown() |
457 | +>>> fixture.tearDown() |
458 | |
459 | Because the database has been destroyed, further tests will not be |
460 | affected. |
461 | |
462 | ->>> PgTestSetup().setUp() |
463 | ->>> connection = PgTestSetup().connect() |
464 | +>>> fixture.setUp() |
465 | +>>> connection = fixture.connect() |
466 | >>> cursor = connection.cursor() |
467 | >>> cursor.execute("CREATE TABLE Beer (id serial PRIMARY KEY, name text)") |
468 | ->>> PgTestSetup().tearDown() |
469 | +>>> fixture.tearDown() |
470 | |
471 | We can also specify a different template to duplicate than the default |
472 | clean one (template1). For example, if you need a launchpad database |
473 | containing no data, you can use `launchpad_empty` as the template. |
474 | |
475 | ->>> PgTestSetup('launchpad_empty').setUp() |
476 | ->>> connection = PgTestSetup().connect() |
477 | +>>> fixture = PgTestSetup('launchpad_empty') |
478 | +>>> fixture.setUp() |
479 | +>>> connection = fixture.connect() |
480 | >>> cursor = connection.cursor() |
481 | >>> cursor.execute("SELECT COUNT(*) FROM Person") |
482 | >>> int(cursor.fetchone()[0]) |
483 | 0 |
484 | ->>> PgTestSetup().tearDown() |
485 | +>>> fixture.tearDown() |
486 | |
487 | We can also specify the user that we connect as to avoid connecting as the |
488 | PostgreSQL default user. |
489 | @@ -108,14 +105,12 @@ |
490 | ------------------ |
491 | |
492 | LaunchpadTestSetup is identical to PgTestSetup, except that it creates a |
493 | -fresh copy of the Launchpad database filled with our sample data. This |
494 | -class is defined in canonical.launchpad.ftests.harness. |
495 | - |
496 | -Note that at this level, you cannot access any of the SQLBase objects |
497 | - |
498 | ->>> from canonical.launchpad.ftests.harness import LaunchpadTestSetup |
499 | ->>> LaunchpadTestSetup().setUp() |
500 | ->>> connection = LaunchpadTestSetup().connect() |
501 | +fresh copy of the Launchpad database filled with our sample data. |
502 | + |
503 | +>>> from canonical.testing.layers import LaunchpadTestSetup |
504 | +>>> fixture = LaunchpadTestSetup() |
505 | +>>> fixture.setUp() |
506 | +>>> connection = fixture.connect() |
507 | >>> cursor = connection.cursor() |
508 | >>> cursor.execute("SELECT displayname FROM person WHERE name='carlos'") |
509 | >>> cursor.fetchone()[0] |
510 | @@ -127,7 +122,7 @@ |
511 | >>> cursor.fetchone()[0] |
512 | u'launchpad' |
513 | |
514 | ->>> LaunchpadTestSetup().tearDown() |
515 | +>>> fixture.tearDown() |
516 | |
517 | You can connect as a different database user using the same mechanism |
518 | described above for PgTestSetup |
519 | @@ -143,114 +138,13 @@ |
520 | >>> lpsetup.tearDown() |
521 | |
522 | |
523 | -LaunchpadZopelessTestSetup |
524 | --------------------------- |
525 | - |
526 | -LaunchpadZopelessTestSetup builds on LaunchpadTestSetup, calling |
527 | -initZopeless for you so you can access the SQLBase objects without needing |
528 | -the Zope3 infrastructure. |
529 | - |
530 | ->>> from canonical.launchpad.ftests.harness import LaunchpadZopelessTestSetup |
531 | ->>> LaunchpadZopelessTestSetup().setUp() |
532 | ->>> from lp.registry.model.person import Person |
533 | ->>> stub = Person.byName('stub') |
534 | ->>> stub.displayname |
535 | -u'Stuart Bishop' |
536 | ->>> stub.displayname = u'The Walrus' |
537 | ->>> stub.displayname |
538 | -u'The Walrus' |
539 | - |
540 | -You have access to the zopeless transaction |
541 | - |
542 | ->>> LaunchpadZopelessTestSetup().txn.abort() |
543 | ->>> stub.displayname |
544 | -u'Stuart Bishop' |
545 | - |
546 | -And always remember to tearDown or you will victimize other tests! |
547 | - |
548 | ->>> LaunchpadZopelessTestSetup().tearDown() |
549 | - |
550 | - |
551 | -In general, Zopeless tests should never be running as the launchpad user. |
552 | -You can select the user you connect as: |
553 | - |
554 | ->>> setup = LaunchpadZopelessTestSetup(dbuser=config.librarian.dbuser) |
555 | ->>> setup.setUp() |
556 | ->>> from lp.registry.model.sourcepackagename import SourcePackageName |
557 | ->>> SourcePackageName.get(1).name |
558 | -Traceback (most recent call last): |
559 | -... |
560 | -ProgrammingError: permission denied for relation sourcepackagename |
561 | -<BLANKLINE> |
562 | ->>> setup.tearDown() |
563 | - |
564 | - |
565 | -LaunchpadFunctionalTestSetup |
566 | ----------------------------- |
567 | - |
568 | -One with the lot. A LaunchpadTestSetup which also loads in the Zope3 |
569 | -environment. |
570 | - |
571 | ->>> from canonical.launchpad.ftests.harness import LaunchpadFunctionalTestSetup |
572 | ->>> LaunchpadFunctionalTestSetup().setUp() |
573 | - |
574 | -You have full access to the SQLBase objects |
575 | - |
576 | ->>> mark = Person.byName('mark') |
577 | ->>> mark.displayname |
578 | -u'Mark Shuttleworth' |
579 | - |
580 | -You also have access to the Zope3 component architecture, as registered |
581 | -by ftesting.zcml |
582 | - |
583 | ->>> from zope.app import zapi |
584 | ->>> from zope.sendmail.interfaces import IMailer |
585 | ->>> zapi.getUtility(IMailer, 'smtp') is not None |
586 | -True |
587 | - |
588 | ->>> LaunchpadFunctionalTestSetup().tearDown() |
589 | - |
590 | -You can change the user that the tests connect as: |
591 | - |
592 | - XXX 2008-05-29 jamesh: |
593 | - Using LaunchpadFunctionalLayer for non-webapp db users is generally |
594 | - a sign of a bug. These bits of code should generally be using |
595 | - LaunchpadZopelessLayer. |
596 | - |
597 | -##>>> setup = LaunchpadFunctionalTestSetup(dbuser=config.librarian.dbuser) |
598 | -##>>> setup.setUp() |
599 | -##>>> connection = setup.connect() |
600 | -##>>> cursor = connection.cursor() |
601 | -##>>> cursor.execute('SELECT current_user') |
602 | -##>>> cursor.fetchone()[0] |
603 | -##u'librarian' |
604 | -##>>> SourcePackageName.get(1).name |
605 | -##Traceback (most recent call last): |
606 | -##... |
607 | -##ProgrammingError: permission denied ... |
608 | -##>>> setup.tearDown() |
609 | - |
610 | -And the next test will be unaffected: |
611 | - |
612 | ->>> setup = LaunchpadFunctionalTestSetup() |
613 | ->>> setup.setUp() |
614 | ->>> connection = setup.connect() |
615 | ->>> cursor = connection.cursor() |
616 | ->>> cursor.execute('SELECT current_user') |
617 | ->>> cursor.fetchone()[0] |
618 | -u'launchpad' |
619 | ->>> SourcePackageName.get(1).name |
620 | -u'mozilla-firefox' |
621 | ->>> setup.tearDown() |
622 | - |
623 | - |
624 | LibrarianTestSetup |
625 | ------------------ |
626 | |
627 | Code that needs to access the Librarian can do so easily. Note that |
628 | LibrarianTestSetup requires the Launchpad database to be available, and |
629 | thus requires LaunchpadTestSetup or similar to be used in tandam. |
630 | -You probably really want LaunchpadFunctionalTestSetup so you can access |
631 | +You probably really want LaunchpadFunctionalLayer so you can access |
632 | the Librarian as a Utility. |
633 | |
634 | >>> from canonical.librarian.testing.server import LibrarianTestSetup |
635 | @@ -259,7 +153,6 @@ |
636 | >>> from canonical.librarian.interfaces import ILibrarianClient |
637 | >>> from StringIO import StringIO |
638 | |
639 | ->>> LaunchpadFunctionalTestSetup().setUp() |
640 | >>> librarian = LibrarianTestSetup() |
641 | >>> librarian.setUp() |
642 | >>> login(ANONYMOUS) |
643 | @@ -285,7 +178,6 @@ |
644 | True |
645 | |
646 | >>> librarian.tearDown() |
647 | ->>> LaunchpadFunctionalTestSetup().tearDown() |
648 | |
649 | >>> from canonical.testing import reset_logging |
650 | >>> reset_logging() |
651 | |
652 | === modified file 'lib/canonical/launchpad/doc/security-proxies.txt' |
653 | --- lib/canonical/launchpad/doc/security-proxies.txt 2010-10-09 16:36:22 +0000 |
654 | +++ lib/canonical/launchpad/doc/security-proxies.txt 2010-10-17 19:12:52 +0000 |
655 | @@ -6,13 +6,10 @@ |
656 | |
657 | First, some imports and set up:: |
658 | |
659 | - >>> from canonical.launchpad.ftests.harness import LaunchpadFunctionalTestSetup |
660 | >>> from zope.component import getUtility |
661 | >>> from lp.registry.interfaces.person import IPersonSet |
662 | >>> from lp.registry.model.person import Person |
663 | |
664 | - >>> LaunchpadFunctionalTestSetup().setUp() |
665 | - |
666 | Get a proxied and unproxied person object for the same person, and demonstrate |
667 | working comparisons:: |
668 | |
669 | @@ -57,8 +54,3 @@ |
670 | True |
671 | >>> hoary.status is SeriesStatus.DEVELOPMENT |
672 | False |
673 | - |
674 | -Finally, tear down the test: |
675 | - |
676 | - >>> LaunchpadFunctionalTestSetup().tearDown() |
677 | - |
678 | |
679 | === removed file 'lib/canonical/launchpad/ftests/harness.py' |
680 | --- lib/canonical/launchpad/ftests/harness.py 2010-10-04 19:50:45 +0000 |
681 | +++ lib/canonical/launchpad/ftests/harness.py 1970-01-01 00:00:00 +0000 |
682 | @@ -1,84 +0,0 @@ |
683 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
684 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
685 | - |
686 | -""" |
687 | -Launchpad functional test helpers. |
688 | - |
689 | -This file needs to be refactored, moving its functionality into |
690 | -canonical.testing |
691 | -""" |
692 | - |
693 | -__metaclass__ = type |
694 | - |
695 | - |
696 | -from zope.app.testing.functional import FunctionalTestSetup |
697 | - |
698 | -from canonical.database.sqlbase import ZopelessTransactionManager |
699 | -from canonical.ftests.pgsql import PgTestSetup |
700 | -from canonical.lp import initZopeless |
701 | -from canonical.testing.layers import ( |
702 | - FunctionalLayer, |
703 | - ZopelessLayer, |
704 | - ) |
705 | -from canonical.testing.layers import ( |
706 | - disconnect_stores, |
707 | - reconnect_stores, |
708 | - ) |
709 | - |
710 | - |
711 | -__all__ = [ |
712 | - 'LaunchpadTestSetup', 'LaunchpadZopelessTestSetup', |
713 | - 'LaunchpadFunctionalTestSetup', |
714 | - ] |
715 | - |
716 | - |
717 | -class LaunchpadTestSetup(PgTestSetup): |
718 | - template = 'launchpad_ftest_template' |
719 | - dbname = 'launchpad_ftest' # Needs to match ftesting.zcml |
720 | - dbuser = 'launchpad' |
721 | - |
722 | - |
723 | -class LaunchpadZopelessTestSetup(LaunchpadTestSetup): |
724 | - txn = ZopelessTransactionManager |
725 | - def setUp(self, dbuser=None): |
726 | - assert ZopelessTransactionManager._installed is None, \ |
727 | - 'Last test using Zopeless failed to tearDown correctly' |
728 | - super(LaunchpadZopelessTestSetup, self).setUp() |
729 | - if self.host is not None: |
730 | - raise NotImplementedError('host not supported yet') |
731 | - if self.port is not None: |
732 | - raise NotImplementedError('port not supported yet') |
733 | - if dbuser is not None: |
734 | - self.dbuser = dbuser |
735 | - initZopeless(dbname=self.dbname, dbuser=self.dbuser) |
736 | - |
737 | - def tearDown(self): |
738 | - LaunchpadZopelessTestSetup.txn.uninstall() |
739 | - assert ZopelessTransactionManager._installed is None, \ |
740 | - 'Failed to tearDown Zopeless correctly' |
741 | - |
742 | - |
743 | -class LaunchpadFunctionalTestSetup(LaunchpadTestSetup): |
744 | - def _checkLayerInvariants(self): |
745 | - assert FunctionalLayer.isSetUp or ZopelessLayer.isSetUp, """ |
746 | - FunctionalTestSetup invoked at an inappropriate time. |
747 | - May only be invoked in the FunctionalLayer or ZopelessLayer |
748 | - """ |
749 | - |
750 | - def setUp(self, dbuser=None): |
751 | - self._checkLayerInvariants() |
752 | - if dbuser is not None: |
753 | - self.dbuser = dbuser |
754 | - assert self.dbuser == 'launchpad', ( |
755 | - "Non-default user names should probably be using " |
756 | - "script layer or zopeless layer.") |
757 | - disconnect_stores() |
758 | - super(LaunchpadFunctionalTestSetup, self).setUp() |
759 | - FunctionalTestSetup().setUp() |
760 | - reconnect_stores() |
761 | - |
762 | - def tearDown(self): |
763 | - self._checkLayerInvariants() |
764 | - FunctionalTestSetup().tearDown() |
765 | - disconnect_stores() |
766 | - super(LaunchpadFunctionalTestSetup, self).tearDown() |
767 | |
768 | === modified file 'lib/canonical/launchpad/pagetests/standalone/xx-dbpolicy.txt' |
769 | --- lib/canonical/launchpad/pagetests/standalone/xx-dbpolicy.txt 2010-01-13 13:50:39 +0000 |
770 | +++ lib/canonical/launchpad/pagetests/standalone/xx-dbpolicy.txt 2010-10-17 19:12:52 +0000 |
771 | @@ -20,9 +20,11 @@ |
772 | >>> from zope.component import getUtility |
773 | >>> from canonical.launchpad.webapp.interfaces import ( |
774 | ... IStoreSelector, MAIN_STORE, MASTER_FLAVOR, SLAVE_FLAVOR) |
775 | + >>> from canonical.testing.layers import DatabaseLayer |
776 | >>> master = getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR) |
777 | - >>> master.execute("SELECT current_database()").get_one()[0] |
778 | - u'launchpad_ftest' |
779 | + >>> dbname = DatabaseLayer._db_fixture.dbname |
780 | + >>> dbname == master.execute("SELECT current_database()").get_one()[0] |
781 | + True |
782 | >>> slave = getUtility(IStoreSelector).get(MAIN_STORE, SLAVE_FLAVOR) |
783 | >>> slave.execute("SELECT current_database()").get_one()[0] |
784 | u'launchpad_empty' |
785 | @@ -47,7 +49,7 @@ |
786 | |
787 | >>> def whichdb(browser): |
788 | ... dbname = extract_text(find_tag_by_id(browser.contents, 'dbname')) |
789 | - ... if dbname == 'launchpad_ftest': |
790 | + ... if dbname == DatabaseLayer._db_fixture.dbname: |
791 | ... return 'MASTER' |
792 | ... elif dbname == 'launchpad_empty': |
793 | ... return 'SLAVE' |
794 | |
795 | === modified file 'lib/canonical/launchpad/tests/test_sampledata.py' |
796 | --- lib/canonical/launchpad/tests/test_sampledata.py 2010-09-22 13:26:50 +0000 |
797 | +++ lib/canonical/launchpad/tests/test_sampledata.py 2010-10-17 19:12:52 +0000 |
798 | @@ -12,7 +12,6 @@ |
799 | __all__ = [] |
800 | |
801 | import subprocess |
802 | -import unittest |
803 | |
804 | from canonical.testing.layers import DatabaseLayer |
805 | from lp.testing import TestCase |
806 | @@ -37,14 +36,11 @@ |
807 | cmd = ( |
808 | "pg_dump --format=c --compress=0 --no-privileges --no-owner" |
809 | " --schema=public %s | pg_restore --clean" |
810 | - " --exit-on-error --dbname=launchpad_ftest" % source_dbname) |
811 | + " --exit-on-error --dbname=%s" % ( |
812 | + source_dbname, DatabaseLayer._db_fixture.dbname)) |
813 | proc = subprocess.Popen( |
814 | cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, |
815 | stdin=subprocess.PIPE) |
816 | (stdout, stderr) = proc.communicate() |
817 | rv = proc.wait() |
818 | self.failUnlessEqual(rv, 0, "Dump/Restore failed: %s" % stdout) |
819 | - |
820 | - |
821 | -def test_suite(): |
822 | - return unittest.TestLoader().loadTestsFromName(__name__) |
823 | |
824 | === modified file 'lib/canonical/launchpad/webapp/ftests/test_adapter.txt' |
825 | --- lib/canonical/launchpad/webapp/ftests/test_adapter.txt 2010-09-17 00:53:33 +0000 |
826 | +++ lib/canonical/launchpad/webapp/ftests/test_adapter.txt 2010-10-17 19:12:52 +0000 |
827 | @@ -18,14 +18,18 @@ |
828 | >>> from canonical.launchpad.webapp.adapter import ( |
829 | ... clear_request_started, get_request_statements, |
830 | ... set_request_started) |
831 | + >>> from canonical.testing.layers import DatabaseLayer |
832 | >>> from lp.services.timeline.requesttimeline import get_request_timeline |
833 | |
834 | There are several possible database connections available via the |
835 | IStoreSelector utility. |
836 | |
837 | >>> store = getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR) |
838 | - >>> print store.execute("SELECT current_database()").get_one()[0] |
839 | - launchpad_ftest |
840 | + >>> dbname = DatabaseLayer._db_fixture.dbname |
841 | + >>> active_name = store.execute("SELECT current_database()").get_one()[0] |
842 | + >>> if active_name != dbname: print '%s != %s' % (active_name, dbname) |
843 | + >>> active_name == dbname |
844 | + True |
845 | |
846 | |
847 | Statement Logging |
848 | |
849 | === modified file 'lib/canonical/lp/ftests/test_zopeless.py' |
850 | --- lib/canonical/lp/ftests/test_zopeless.py 2010-10-04 19:50:45 +0000 |
851 | +++ lib/canonical/lp/ftests/test_zopeless.py 2010-10-17 19:12:52 +0000 |
852 | @@ -14,9 +14,11 @@ |
853 | from sqlobject import StringCol, IntCol |
854 | |
855 | from canonical.database.sqlbase import SQLBase, alreadyInstalledMsg, cursor |
856 | -from canonical.ftests.pgsql import PgTestSetup |
857 | from canonical.lp import initZopeless |
858 | -from canonical.testing.layers import LaunchpadScriptLayer |
859 | +from canonical.testing.layers import ( |
860 | + DatabaseLayer, |
861 | + LaunchpadScriptLayer, |
862 | + ) |
863 | |
864 | |
865 | class MoreBeer(SQLBase): |
866 | @@ -28,6 +30,7 @@ |
867 | |
868 | |
869 | class TestInitZopeless(unittest.TestCase): |
870 | + |
871 | layer = LaunchpadScriptLayer |
872 | |
873 | def test_initZopelessTwice(self): |
874 | @@ -47,10 +50,11 @@ |
875 | # Calling initZopeless with the same arguments twice should return |
876 | # the exact same object twice, but also emit a warning. |
877 | try: |
878 | - tm1 = initZopeless(dbname=PgTestSetup().dbname, dbhost='', |
879 | - dbuser='launchpad') |
880 | - tm2 = initZopeless(dbname=PgTestSetup().dbname, dbhost='', |
881 | - dbuser='launchpad') |
882 | + dbname = DatabaseLayer._db_fixture.dbname |
883 | + tm1 = initZopeless( |
884 | + dbname=dbname, dbhost='', dbuser='launchpad') |
885 | + tm2 = initZopeless( |
886 | + dbname=dbname, dbhost='', dbuser='launchpad') |
887 | self.failUnless(tm1 is tm2) |
888 | self.failUnless(self.warned) |
889 | finally: |
890 | @@ -65,10 +69,11 @@ |
891 | |
892 | |
893 | class TestZopeless(unittest.TestCase): |
894 | + |
895 | layer = LaunchpadScriptLayer |
896 | |
897 | def setUp(self): |
898 | - self.tm = initZopeless(dbname=PgTestSetup().dbname, |
899 | + self.tm = initZopeless(dbname=DatabaseLayer._db_fixture.dbname, |
900 | dbuser='launchpad') |
901 | |
902 | c = cursor() |
903 | @@ -182,7 +187,7 @@ |
904 | self.tm.commit() |
905 | |
906 | # Make another change from a non-SQLObject connection, and commit that |
907 | - conn = psycopg2.connect('dbname=' + PgTestSetup().dbname) |
908 | + conn = psycopg2.connect('dbname=' + DatabaseLayer._db_fixture.dbname) |
909 | cur = conn.cursor() |
910 | cur.execute("BEGIN TRANSACTION;") |
911 | cur.execute("UPDATE MoreBeer SET rating=4 " |
912 | @@ -202,7 +207,7 @@ |
913 | >>> isZopeless() |
914 | False |
915 | |
916 | - >>> tm = initZopeless(dbname=PgTestSetup().dbname, |
917 | + >>> tm = initZopeless(dbname=DatabaseLayer._db_fixture.dbname, |
918 | ... dbhost='', dbuser='launchpad') |
919 | >>> isZopeless() |
920 | True |
921 | |
922 | === modified file 'lib/canonical/testing/ftests/test_layers.py' |
923 | --- lib/canonical/testing/ftests/test_layers.py 2010-07-26 13:18:18 +0000 |
924 | +++ lib/canonical/testing/ftests/test_layers.py 2010-10-17 19:12:52 +0000 |
925 | @@ -20,16 +20,27 @@ |
926 | from zope.component import getUtility, ComponentLookupError |
927 | |
928 | from canonical.config import config, dbconfig |
929 | -from canonical.launchpad.ftests.harness import LaunchpadTestSetup |
930 | from lazr.config import as_host_port |
931 | from canonical.librarian.client import LibrarianClient, UploadFailed |
932 | from canonical.librarian.interfaces import ILibrarianClient |
933 | from canonical.lazr.pidfile import pidfile_path |
934 | from canonical.testing.layers import ( |
935 | - AppServerLayer, BaseLayer, DatabaseLayer, FunctionalLayer, |
936 | - LaunchpadFunctionalLayer, LaunchpadLayer, LaunchpadScriptLayer, |
937 | - LaunchpadZopelessLayer, LayerInvariantError, LayerIsolationError, |
938 | - LayerProcessController, LibrarianLayer, MemcachedLayer, ZopelessLayer) |
939 | + AppServerLayer, |
940 | + BaseLayer, |
941 | + DatabaseLayer, |
942 | + FunctionalLayer, |
943 | + LaunchpadFunctionalLayer, |
944 | + LaunchpadLayer, |
945 | + LaunchpadScriptLayer, |
946 | + LaunchpadTestSetup, |
947 | + LaunchpadZopelessLayer, |
948 | + LayerInvariantError, |
949 | + LayerIsolationError, |
950 | + LayerProcessController, |
951 | + LibrarianLayer, |
952 | + MemcachedLayer, |
953 | + ZopelessLayer, |
954 | + ) |
955 | from lp.services.memcache.client import memcache_client_factory |
956 | |
957 | class BaseTestCase(unittest.TestCase): |
958 | @@ -123,22 +134,13 @@ |
959 | ) |
960 | |
961 | def testLaunchpadDbAvailable(self): |
962 | - try: |
963 | - con = DatabaseLayer.connect() |
964 | - cur = con.cursor() |
965 | - cur.execute("SELECT id FROM Person LIMIT 1") |
966 | - if cur.fetchone() is not None: |
967 | - self.failUnless( |
968 | - self.want_launchpad_database, |
969 | - 'Launchpad database should not be available.' |
970 | - ) |
971 | - return |
972 | - except psycopg2.Error: |
973 | - pass |
974 | - self.failIf( |
975 | - self.want_launchpad_database, |
976 | - 'Launchpad database should be available but is not.' |
977 | - ) |
978 | + if not self.want_launchpad_database: |
979 | + self.assertEqual(None, DatabaseLayer._db_fixture) |
980 | + return |
981 | + con = DatabaseLayer.connect() |
982 | + cur = con.cursor() |
983 | + cur.execute("SELECT id FROM Person LIMIT 1") |
984 | + self.assertNotEqual(None, cur.fetchone()) |
985 | |
986 | def testMemcachedWorking(self): |
987 | client = MemcachedLayer.client or memcache_client_factory() |
988 | @@ -424,6 +426,9 @@ |
989 | # The database should be reset by the test invariants. |
990 | LayerProcessController.startAppServer() |
991 | LayerProcessController.postTestInvariants() |
992 | + # XXX: Robert Collins 2010-10-17 bug=661967 - this isn't a reset, its |
993 | + # a flag that it *needs* a reset, which is actually quite different; |
994 | + # the lack of a teardown will leak daabases. |
995 | self.assertEquals(True, LaunchpadTestSetup()._reset_db) |
996 | |
997 | |
998 | |
999 | === modified file 'lib/canonical/testing/layers.py' |
1000 | --- lib/canonical/testing/layers.py 2010-10-05 13:25:01 +0000 |
1001 | +++ lib/canonical/testing/layers.py 2010-10-17 19:12:52 +0000 |
1002 | @@ -35,6 +35,7 @@ |
1003 | 'LaunchpadFunctionalLayer', |
1004 | 'LaunchpadLayer', |
1005 | 'LaunchpadScriptLayer', |
1006 | + 'LaunchpadTestSetup', |
1007 | 'LaunchpadZopelessLayer', |
1008 | 'LayerInvariantError', |
1009 | 'LayerIsolationError', |
1010 | @@ -92,6 +93,7 @@ |
1011 | from zope.server.logger.pythonlogger import PythonLogger |
1012 | from zope.testing.testrunner.runner import FakeInputContinueGenerator |
1013 | |
1014 | +from canonical.ftests.pgsql import PgTestSetup |
1015 | from canonical.launchpad.webapp.vhosts import allvhosts |
1016 | from canonical.lazr import pidfile |
1017 | from canonical.config import CanonicalConfig, config, dbconfig |
1018 | @@ -264,12 +266,14 @@ |
1019 | if not BaseLayer.persist_test_services: |
1020 | kill_by_pidfile(MemcachedLayer.getPidFile(), num_polls=0) |
1021 | # Kill any database left lying around from a previous test run. |
1022 | + db_fixture = LaunchpadTestSetup() |
1023 | try: |
1024 | - DatabaseLayer.connect().close() |
1025 | + db_fixture.connect().close() |
1026 | except psycopg2.Error: |
1027 | + # We assume this means 'no test database exists.' |
1028 | pass |
1029 | else: |
1030 | - DatabaseLayer._dropDb() |
1031 | + db_fixture.dropDb() |
1032 | |
1033 | @classmethod |
1034 | @profiled |
1035 | @@ -693,19 +697,19 @@ |
1036 | _reset_between_tests = True |
1037 | |
1038 | _is_setup = False |
1039 | + _db_fixture = None |
1040 | |
1041 | @classmethod |
1042 | @profiled |
1043 | def setUp(cls): |
1044 | cls._is_setup = True |
1045 | - DatabaseLayer.force_dirty_database() |
1046 | - # Imported here to avoid circular import issues. This |
1047 | - # functionality should be migrated into this module at some |
1048 | - # point. -- StuartBishop 20060712 |
1049 | - from canonical.launchpad.ftests.harness import LaunchpadTestSetup |
1050 | - LaunchpadTestSetup().tearDown() |
1051 | - DatabaseLayer._reset_sequences_sql = LaunchpadTestSetup( |
1052 | + # Read the sequences we'll need from the test template database. |
1053 | + reset_sequences_sql = LaunchpadTestSetup( |
1054 | dbname='launchpad_ftest_template').generateResetSequencesSQL() |
1055 | + cls._db_fixture = LaunchpadTestSetup( |
1056 | + reset_sequences_sql=reset_sequences_sql) |
1057 | + cls.force_dirty_database() |
1058 | + cls._db_fixture.tearDown() |
1059 | |
1060 | @classmethod |
1061 | @profiled |
1062 | @@ -716,32 +720,22 @@ |
1063 | # Don't leave the DB lying around or it might break tests |
1064 | # that depend on it not being there on startup, such as found |
1065 | # in test_layers.py |
1066 | - DatabaseLayer.force_dirty_database() |
1067 | - # Imported here to avoid circular import issues. This |
1068 | - # functionality should be migrated into this module at some |
1069 | - # point. -- StuartBishop 20060712 |
1070 | - from canonical.launchpad.ftests.harness import LaunchpadTestSetup |
1071 | - LaunchpadTestSetup().tearDown() |
1072 | - DatabaseLayer._reset_sequences_sql = None |
1073 | + cls.force_dirty_database() |
1074 | + cls._db_fixture.tearDown() |
1075 | + cls._db_fixture = None |
1076 | |
1077 | @classmethod |
1078 | @profiled |
1079 | def testSetUp(cls): |
1080 | - # Imported here to avoid circular import issues. This |
1081 | - # functionality should be migrated into this module at some |
1082 | - # point. -- StuartBishop 20060712 |
1083 | - from canonical.launchpad.ftests.harness import LaunchpadTestSetup |
1084 | - if DatabaseLayer._reset_between_tests: |
1085 | - LaunchpadTestSetup( |
1086 | - reset_sequences_sql=DatabaseLayer._reset_sequences_sql |
1087 | - ).setUp() |
1088 | + if cls._reset_between_tests: |
1089 | + cls._db_fixture.setUp() |
1090 | # Ensure that the database is connectable. Because we might have |
1091 | # just created it, keep trying for a few seconds incase PostgreSQL |
1092 | # is taking its time getting its house in order. |
1093 | attempts = 60 |
1094 | for count in range(0, attempts): |
1095 | try: |
1096 | - DatabaseLayer.connect().close() |
1097 | + cls.connect().close() |
1098 | except psycopg2.Error: |
1099 | if count == attempts - 1: |
1100 | raise |
1101 | @@ -749,24 +743,20 @@ |
1102 | else: |
1103 | break |
1104 | |
1105 | - if DatabaseLayer.use_mockdb is True: |
1106 | - DatabaseLayer.installMockDb() |
1107 | + if cls.use_mockdb is True: |
1108 | + cls.installMockDb() |
1109 | |
1110 | @classmethod |
1111 | @profiled |
1112 | def testTearDown(cls): |
1113 | - if DatabaseLayer.use_mockdb is True: |
1114 | - DatabaseLayer.uninstallMockDb() |
1115 | + if cls.use_mockdb is True: |
1116 | + cls.uninstallMockDb() |
1117 | |
1118 | # Ensure that the database is connectable |
1119 | - DatabaseLayer.connect().close() |
1120 | + cls.connect().close() |
1121 | |
1122 | - # Imported here to avoid circular import issues. This |
1123 | - # functionality should be migrated into this module at some |
1124 | - # point. -- StuartBishop 20060712 |
1125 | - from canonical.launchpad.ftests.harness import LaunchpadTestSetup |
1126 | - if DatabaseLayer._reset_between_tests: |
1127 | - LaunchpadTestSetup().tearDown() |
1128 | + if cls._reset_between_tests: |
1129 | + cls._db_fixture.tearDown() |
1130 | |
1131 | # Fail tests that forget to uninstall their database policies. |
1132 | from canonical.launchpad.webapp.adapter import StoreSelector |
1133 | @@ -781,7 +771,7 @@ |
1134 | @classmethod |
1135 | @profiled |
1136 | def installMockDb(cls): |
1137 | - assert DatabaseLayer.mockdb_mode is None, 'mock db already installed' |
1138 | + assert cls.mockdb_mode is None, 'mock db already installed' |
1139 | |
1140 | from canonical.testing.mockdb import ( |
1141 | script_filename, ScriptRecorder, ScriptPlayer, |
1142 | @@ -795,32 +785,32 @@ |
1143 | # mock db script. |
1144 | filename = script_filename(test_key) |
1145 | if os.path.exists(filename): |
1146 | - DatabaseLayer.mockdb_mode = 'replay' |
1147 | - DatabaseLayer.script = ScriptPlayer(test_key) |
1148 | + cls.mockdb_mode = 'replay' |
1149 | + cls.script = ScriptPlayer(test_key) |
1150 | else: |
1151 | - DatabaseLayer.mockdb_mode = 'record' |
1152 | - DatabaseLayer.script = ScriptRecorder(test_key) |
1153 | + cls.mockdb_mode = 'record' |
1154 | + cls.script = ScriptRecorder(test_key) |
1155 | |
1156 | global _org_connect |
1157 | _org_connect = psycopg2.connect |
1158 | # Proxy real connections with our mockdb. |
1159 | def fake_connect(*args, **kw): |
1160 | - return DatabaseLayer.script.connect(_org_connect, *args, **kw) |
1161 | + return cls.script.connect(_org_connect, *args, **kw) |
1162 | psycopg2.connect = fake_connect |
1163 | |
1164 | @classmethod |
1165 | @profiled |
1166 | def uninstallMockDb(cls): |
1167 | - if DatabaseLayer.mockdb_mode is None: |
1168 | + if cls.mockdb_mode is None: |
1169 | return # Already uninstalled |
1170 | |
1171 | # Store results if we are recording |
1172 | - if DatabaseLayer.mockdb_mode == 'record': |
1173 | - DatabaseLayer.script.store() |
1174 | - assert os.path.exists(DatabaseLayer.script.script_filename), ( |
1175 | + if cls.mockdb_mode == 'record': |
1176 | + cls.script.store() |
1177 | + assert os.path.exists(cls.script.script_filename), ( |
1178 | "Stored results but no script on disk.") |
1179 | |
1180 | - DatabaseLayer.mockdb_mode = None |
1181 | + cls.mockdb_mode = None |
1182 | global _org_connect |
1183 | psycopg2.connect = _org_connect |
1184 | _org_connect = None |
1185 | @@ -828,20 +818,17 @@ |
1186 | @classmethod |
1187 | @profiled |
1188 | def force_dirty_database(cls): |
1189 | - from canonical.launchpad.ftests.harness import LaunchpadTestSetup |
1190 | - LaunchpadTestSetup().force_dirty_database() |
1191 | + cls._db_fixture.force_dirty_database() |
1192 | |
1193 | @classmethod |
1194 | @profiled |
1195 | def connect(cls): |
1196 | - from canonical.launchpad.ftests.harness import LaunchpadTestSetup |
1197 | - return LaunchpadTestSetup().connect() |
1198 | + return cls._db_fixture.connect() |
1199 | |
1200 | @classmethod |
1201 | @profiled |
1202 | def _dropDb(cls): |
1203 | - from canonical.launchpad.ftests.harness import LaunchpadTestSetup |
1204 | - return LaunchpadTestSetup().dropDb() |
1205 | + return cls._db_fixture.dropDb() |
1206 | |
1207 | |
1208 | def test_default_timeout(): |
1209 | @@ -1378,6 +1365,11 @@ |
1210 | reconnect_stores(database_config_section=database_config_section) |
1211 | |
1212 | |
1213 | +class LaunchpadTestSetup(PgTestSetup): |
1214 | + template = 'launchpad_ftest_template' |
1215 | + dbuser = 'launchpad' |
1216 | + |
1217 | + |
1218 | class LaunchpadZopelessLayer(LaunchpadScriptLayer): |
1219 | """Full Zopeless environment including Component Architecture and |
1220 | database connections initialized. |
1221 | @@ -1643,6 +1635,9 @@ |
1222 | # configs/testrunner-appserver/mail-configure.zcml |
1223 | smtp_controller = None |
1224 | |
1225 | + # The DB fixture in use |
1226 | + _db_fixture = None |
1227 | + |
1228 | @classmethod |
1229 | @profiled |
1230 | def startSMTPServer(cls): |
1231 | @@ -1770,9 +1765,12 @@ |
1232 | @classmethod |
1233 | def _runAppServer(cls): |
1234 | """Start the app server using runlaunchpad.py""" |
1235 | - from canonical.launchpad.ftests.harness import LaunchpadTestSetup |
1236 | # The database must be available for the app server to start. |
1237 | - LaunchpadTestSetup().setUp() |
1238 | + cls._db_fixture = LaunchpadTestSetup() |
1239 | + # This is not torn down properly: rather the singleton nature is abused |
1240 | + # and the fixture is simply marked as being dirty. |
1241 | + # XXX: Robert Collins 2010-10-17 bug=661967 |
1242 | + cls._db_fixture.setUp() |
1243 | # The app server will not start at all if the database hasn't been |
1244 | # correctly patched. The app server will make exactly this check, |
1245 | # doing it here makes the error more obvious. |
1246 | |
1247 | === modified file 'lib/lp/codehosting/tests/test_acceptance.py' |
1248 | --- lib/lp/codehosting/tests/test_acceptance.py 2010-10-04 19:50:45 +0000 |
1249 | +++ lib/lp/codehosting/tests/test_acceptance.py 2010-10-17 19:12:52 +0000 |
1250 | @@ -21,7 +21,6 @@ |
1251 | from zope.component import getUtility |
1252 | |
1253 | from canonical.config import config |
1254 | -from canonical.launchpad.ftests.harness import LaunchpadZopelessTestSetup |
1255 | from canonical.testing.layers import ZopelessAppServerLayer |
1256 | from canonical.testing.profiled import profiled |
1257 | from lp.code.bzr import ( |
1258 | @@ -334,7 +333,7 @@ |
1259 | remote_url = self.getTransportURL('~testuser/+junk/test-branch') |
1260 | self.push(self.local_branch_path, remote_url) |
1261 | self.assertBranchesMatch(self.local_branch_path, remote_url) |
1262 | - LaunchpadZopelessTestSetup().txn.begin() |
1263 | + ZopelessAppServerLayer.txn.begin() |
1264 | db_branch = getUtility(IBranchSet).getByUniqueName( |
1265 | '~testuser/+junk/test-branch') |
1266 | self.assertEqual( |
1267 | @@ -343,7 +342,7 @@ |
1268 | BranchFormat.BZR_BRANCH_7, db_branch.branch_format) |
1269 | self.assertEqual( |
1270 | ControlFormat.BZR_METADIR_1, db_branch.control_format) |
1271 | - LaunchpadZopelessTestSetup().txn.commit() |
1272 | + ZopelessAppServerLayer.txn.commit() |
1273 | |
1274 | def test_push_to_existing_branch(self): |
1275 | """Pushing to an existing branch must work.""" |
1276 | @@ -374,12 +373,12 @@ |
1277 | self.push(self.local_branch_path, remote_url) |
1278 | |
1279 | # Rename owner, product and branch in the database |
1280 | - LaunchpadZopelessTestSetup().txn.begin() |
1281 | + ZopelessAppServerLayer.txn.begin() |
1282 | branch = self.getDatabaseBranch('testuser', None, 'test-branch') |
1283 | branch.owner.name = 'renamed-user' |
1284 | branch.setTarget(user=branch.owner, project=Product.byName('firefox')) |
1285 | branch.name = 'renamed-branch' |
1286 | - LaunchpadZopelessTestSetup().txn.commit() |
1287 | + ZopelessAppServerLayer.txn.commit() |
1288 | |
1289 | # Check that it's not at the old location. |
1290 | self.assertNotBranch( |
1291 | @@ -405,23 +404,23 @@ |
1292 | '~testuser/+junk/totally-new-branch') |
1293 | self.push(self.local_branch_path, remote_url) |
1294 | |
1295 | - LaunchpadZopelessTestSetup().txn.begin() |
1296 | + ZopelessAppServerLayer.txn.begin() |
1297 | branch = self.getDatabaseBranch( |
1298 | 'testuser', None, 'totally-new-branch') |
1299 | |
1300 | self.assertEqual( |
1301 | ['~testuser/+junk/totally-new-branch', self.revid], |
1302 | [branch.unique_name, branch.last_mirrored_id]) |
1303 | - LaunchpadZopelessTestSetup().txn.abort() |
1304 | + ZopelessAppServerLayer.txn.abort() |
1305 | |
1306 | def test_record_default_stacking(self): |
1307 | # If the location being pushed to has a default stacked-on branch, |
1308 | # then branches pushed to that location end up stacked on it by |
1309 | # default. |
1310 | product = self.factory.makeProduct() |
1311 | - LaunchpadZopelessTestSetup().txn.commit() |
1312 | + ZopelessAppServerLayer.txn.commit() |
1313 | |
1314 | - LaunchpadZopelessTestSetup().txn.begin() |
1315 | + ZopelessAppServerLayer.txn.begin() |
1316 | |
1317 | self.make_branch_and_tree('stacked-on') |
1318 | trunk_unique_name = '~testuser/%s/trunk' % product.name |
1319 | @@ -431,7 +430,7 @@ |
1320 | self.factory.enableDefaultStackingForProduct( |
1321 | db_trunk.product, db_trunk) |
1322 | |
1323 | - LaunchpadZopelessTestSetup().txn.commit() |
1324 | + ZopelessAppServerLayer.txn.commit() |
1325 | |
1326 | stacked_unique_name = '~testuser/%s/stacked' % product.name |
1327 | self.push( |
1328 | @@ -447,7 +446,7 @@ |
1329 | # attribute of the database branch, and stacked on location of the new |
1330 | # branch is normalized to be a relative path. |
1331 | product = self.factory.makeProduct() |
1332 | - LaunchpadZopelessTestSetup().txn.commit() |
1333 | + ZopelessAppServerLayer.txn.commit() |
1334 | |
1335 | self.make_branch_and_tree('stacked-on') |
1336 | trunk_unique_name = '~testuser/%s/trunk' % product.name |
1337 | @@ -507,11 +506,11 @@ |
1338 | def test_push_to_new_short_branch_alias(self): |
1339 | # We can also push branches to URLs like /+branch/firefox |
1340 | # Hack 'firefox' so we have permission to do this. |
1341 | - LaunchpadZopelessTestSetup().txn.begin() |
1342 | + ZopelessAppServerLayer.txn.begin() |
1343 | firefox = Product.selectOneBy(name='firefox') |
1344 | testuser = Person.selectOneBy(name='testuser') |
1345 | firefox.development_focus.owner = testuser |
1346 | - LaunchpadZopelessTestSetup().txn.commit() |
1347 | + ZopelessAppServerLayer.txn.commit() |
1348 | remote_url = self.getTransportURL('+branch/firefox') |
1349 | self.push(self.local_branch_path, remote_url) |
1350 | self.assertBranchesMatch(self.local_branch_path, remote_url) |
1351 | @@ -520,10 +519,10 @@ |
1352 | # If a hosted branch exists in the database, but not on the |
1353 | # filesystem, and is writable by the user, then the user is able to |
1354 | # push to it. |
1355 | - LaunchpadZopelessTestSetup().txn.begin() |
1356 | + ZopelessAppServerLayer.txn.begin() |
1357 | branch = self.makeDatabaseBranch('testuser', 'firefox', 'some-branch') |
1358 | remote_url = self.getTransportURL(branch.unique_name) |
1359 | - LaunchpadZopelessTestSetup().txn.commit() |
1360 | + ZopelessAppServerLayer.txn.commit() |
1361 | self.push( |
1362 | self.local_branch_path, remote_url, |
1363 | extra_args=['--use-existing-dir']) |
1364 | @@ -531,21 +530,21 @@ |
1365 | |
1366 | def test_cant_push_to_existing_mirrored_branch(self): |
1367 | # Users cannot push to mirrored branches. |
1368 | - LaunchpadZopelessTestSetup().txn.begin() |
1369 | + ZopelessAppServerLayer.txn.begin() |
1370 | branch = self.makeDatabaseBranch( |
1371 | 'testuser', 'firefox', 'some-branch', BranchType.MIRRORED) |
1372 | remote_url = self.getTransportURL(branch.unique_name) |
1373 | - LaunchpadZopelessTestSetup().txn.commit() |
1374 | + ZopelessAppServerLayer.txn.commit() |
1375 | self.assertCantPush( |
1376 | self.local_branch_path, remote_url, |
1377 | ['Permission denied:', 'Transport operation not possible:']) |
1378 | |
1379 | def test_cant_push_to_existing_unowned_hosted_branch(self): |
1380 | # Users can only push to hosted branches that they own. |
1381 | - LaunchpadZopelessTestSetup().txn.begin() |
1382 | + ZopelessAppServerLayer.txn.begin() |
1383 | branch = self.makeDatabaseBranch('mark', 'firefox', 'some-branch') |
1384 | remote_url = self.getTransportURL(branch.unique_name) |
1385 | - LaunchpadZopelessTestSetup().txn.commit() |
1386 | + ZopelessAppServerLayer.txn.commit() |
1387 | self.assertCantPush( |
1388 | self.local_branch_path, remote_url, |
1389 | ['Permission denied:', 'Transport operation not possible:']) |
1390 | @@ -566,12 +565,12 @@ |
1391 | person_name, product_name, branch_name) |
1392 | |
1393 | # Mark as mirrored. |
1394 | - LaunchpadZopelessTestSetup().txn.begin() |
1395 | + ZopelessAppServerLayer.txn.begin() |
1396 | branch = self.getDatabaseBranch( |
1397 | person_name, product_name, branch_name) |
1398 | branch.branch_type = BranchType.MIRRORED |
1399 | branch.url = "http://example.com/smartservertest/branch" |
1400 | - LaunchpadZopelessTestSetup().txn.commit() |
1401 | + ZopelessAppServerLayer.txn.commit() |
1402 | return ro_branch_url |
1403 | |
1404 | def test_can_read_readonly_branch(self): |
1405 | |
1406 | === modified file 'lib/lp/soyuz/doc/sampledata-setup.txt' |
1407 | --- lib/lp/soyuz/doc/sampledata-setup.txt 2010-08-13 02:59:14 +0000 |
1408 | +++ lib/lp/soyuz/doc/sampledata-setup.txt 2010-10-17 19:12:52 +0000 |
1409 | @@ -21,5 +21,5 @@ |
1410 | INFO ... |
1411 | INFO Done. |
1412 | |
1413 | - >>> from canonical.launchpad.ftests.harness import LaunchpadTestSetup |
1414 | - >>> LaunchpadTestSetup().force_dirty_database() |
1415 | + >>> from canonical.testing.layers import DatabaseLayer |
1416 | + >>> DatabaseLayer.force_dirty_database() |
1417 | |
1418 | === modified file 'lib/lp/soyuz/scripts/tests/test_buildd_cronscripts.py' |
1419 | --- lib/lp/soyuz/scripts/tests/test_buildd_cronscripts.py 2010-10-04 19:50:45 +0000 |
1420 | +++ lib/lp/soyuz/scripts/tests/test_buildd_cronscripts.py 2010-10-17 19:12:52 +0000 |
1421 | @@ -74,9 +74,8 @@ |
1422 | rc, out, err = runner() |
1423 | self.assertEqual(0, rc, "Err:\n%s" % err) |
1424 | |
1425 | - # 'runners' commit to the launchpad_ftest database in |
1426 | - # subprocesses, so we need to tell the layer to fully |
1427 | - # tear down and restore the database. |
1428 | + # 'runners' commit to the test database in subprocesses, so we need to |
1429 | + # tell the layer to fully tear down and restore the database. |
1430 | DatabaseLayer.force_dirty_database() |
1431 | |
1432 | return rc, out, err |
1433 | |
1434 | === modified file 'lib/lp/translations/doc/fix_translation_credits.txt' |
1435 | --- lib/lp/translations/doc/fix_translation_credits.txt 2010-04-01 04:05:10 +0000 |
1436 | +++ lib/lp/translations/doc/fix_translation_credits.txt 2010-10-17 19:12:52 +0000 |
1437 | @@ -19,5 +19,5 @@ |
1438 | After altering the database from a separate process, we must tell the |
1439 | test setup that the database is dirty in spite of appearances. |
1440 | |
1441 | - >>> from canonical.launchpad.ftests.harness import LaunchpadTestSetup |
1442 | - >>> LaunchpadTestSetup().force_dirty_database() |
1443 | + >>> from canonical.testing.layers import DatabaseLayer |
1444 | + >>> DatabaseLayer.force_dirty_database() |
1445 | |
1446 | === modified file 'lib/lp/translations/doc/message-sharing-merge-script.txt' |
1447 | --- lib/lp/translations/doc/message-sharing-merge-script.txt 2009-08-04 13:37:57 +0000 |
1448 | +++ lib/lp/translations/doc/message-sharing-merge-script.txt 2010-10-17 19:12:52 +0000 |
1449 | @@ -20,5 +20,5 @@ |
1450 | # The script modified the database, even though the database layer may |
1451 | # not have noticed it. |
1452 | |
1453 | - >>> from canonical.launchpad.ftests.harness import LaunchpadTestSetup |
1454 | - >>> LaunchpadTestSetup().force_dirty_database() |
1455 | + >>> from canonical.testing.layers import DatabaseLayer |
1456 | + >>> DatabaseLayer.force_dirty_database() |
1457 | |
1458 | === modified file 'lib/lp/translations/doc/request_country.txt' |
1459 | --- lib/lp/translations/doc/request_country.txt 2010-02-26 21:58:15 +0000 |
1460 | +++ lib/lp/translations/doc/request_country.txt 2010-10-17 19:12:52 +0000 |
1461 | @@ -4,10 +4,6 @@ |
1462 | |
1463 | Adapting a request to a country allows you to see where the request came from. |
1464 | |
1465 | - >>> from canonical.launchpad.ftests.harness import ( |
1466 | - ... LaunchpadFunctionalTestSetup) |
1467 | - >>> LaunchpadFunctionalTestSetup().setUp() |
1468 | - |
1469 | Here's a dummy request. Zope adds the REMOTE_ADDR CGI environment variable |
1470 | for us. Upstream proxy servers (and tinkering users!) may also add |
1471 | X-Forwarded-For: headers. The X-Forwarded-For: header takes precidence |
1472 | @@ -34,6 +30,3 @@ |
1473 | Traceback (most recent call last): |
1474 | ... |
1475 | TypeError: ('Could not adapt', ... |
1476 | - |
1477 | - >>> LaunchpadFunctionalTestSetup().tearDown() |
1478 | - |
On Sun, Oct 17, 2010 at 7:59 AM, Robert Collins
<email address hidden> wrote:
> Robert Collins has proposed merging lp:~lifeless/launchpad/databasefixture into lp:launchpad/devel.
>
> Requested reviews:
> Jonathan Lange (jml)
>
>
> This makes the database test support layer ready for parallel testing - we can run with unique database names. Not supported yet is having the config system unique for a test process (I'd make it more granular than that, but zcml is so global that I don't think thats feasible until we reengineer that entire stack.
>
> Some things were no longer used, so I deleted them. Most things were trivial.
>
> We won't see any test DB leaks at this point, but I'm worried about the possibility when we turn this on... still thats a problem for another day.
As you said on IRC, it's a mostly mechanical branch. The changes all
look good to me, and definitely in the right direction.
Land when ready.
jml