Merge lp:~lifeless/launchpad/databasefixture into lp:launchpad

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
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 :

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

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-