Merge lp:~stevenk/launchpad/db-add-derivedistroseries-api into lp:launchpad/db-devel

Proposed by Steve Kowalik
Status: Merged
Approved by: Graham Binns
Approved revision: no longer in the source branch.
Merged at revision: 9903
Proposed branch: lp:~stevenk/launchpad/db-add-derivedistroseries-api
Merge into: lp:launchpad/db-devel
Prerequisite: lp:~stevenk/launchpad/db-add-parameters-to-idsjob
Diff against target: 2430 lines (+563/-752)
42 files modified
cronscripts/garbo-daily.py (+1/-1)
cronscripts/garbo-hourly.py (+1/-1)
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/interfaces/_schema_circular_imports.py (+2/-0)
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/bugs/doc/bug-heat.txt (+1/-1)
lib/lp/bugs/tests/test_bugwatch.py (+1/-1)
lib/lp/code/model/tests/test_revision.py (+1/-1)
lib/lp/code/model/tests/test_revisionauthor.py (+1/-1)
lib/lp/code/scripts/tests/test_revisionkarma.py (+1/-1)
lib/lp/codehosting/tests/test_acceptance.py (+20/-21)
lib/lp/hardwaredb/doc/hwdb.txt (+1/-1)
lib/lp/poppy/tests/test_poppy.py (+3/-3)
lib/lp/registry/interfaces/distroseries.py (+80/-4)
lib/lp/registry/model/distroseries.py (+62/-0)
lib/lp/registry/stories/webservice/xx-derivedistroseries.txt (+68/-0)
lib/lp/registry/tests/test_derivedistroseries.py (+76/-0)
lib/lp/scripts/tests/test_garbo.py (+1/-1)
lib/lp/scripts/utilities/importfascist.py (+1/-1)
lib/lp/soyuz/configure.zcml (+5/-2)
lib/lp/soyuz/doc/sampledata-setup.txt (+2/-2)
lib/lp/soyuz/scripts/initialise_distroseries.py (+2/-2)
lib/lp/soyuz/scripts/tests/test_buildd_cronscripts.py (+2/-3)
lib/lp/testing/__init__.py (+1/-15)
lib/lp/testing/fixture.py (+7/-118)
lib/lp/testing/tests/test_fixture.py (+0/-138)
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:~stevenk/launchpad/db-add-derivedistroseries-api
Reviewer Review Type Date Requested Status
Graham Binns (community) code Approve
Review via email: mp+38189@code.launchpad.net

Commit message

Allow derivers to create a derived distroseries via the API.

Description of the change

This branch adds a new method to IDistroSeries: deriveDistroSeries().

This method is intended to be called via API scripts for both the distro use-case as well as the Linaro use-case to initialise a new distroseries (which may or may not exist), from an existing distroseries. It will create the new distroseries if it doesn't exist, under the specified distribution, unless it isn't specified, in which case it will use the current distroseries's distribution (.parent in the interface).

After it has verified everything, and created the distroseries if need be, it checks the details submitted via the InitialiseDistroSeries.check() method, and if that doesn't raise an InitislationError exception, an InitialiseDistroSeriesJob is created. The method doesn't return the job id, since there is no way for the user to check the status of the job.

I have written two seperate tests, one that calls the method directly and tests it, and a lighter doctest that checks via the API.

I have discussed this change with both Julian and Michael, and they agreed that it sounded good.

To test: bin/test -vvt test_derivedistroseries -t xx-derivedistroseries.txt

This branch also drive-bys two mis-spellings of distribution, since I noticed them.

This MP supersedes the one linked at https://code.edge.launchpad.net/~stevenk/launchpad/db-add-derivedistroseries-api/+merge/35500, which was approved by Gavin.

To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) wrote :

Hi Steve,

Overall I'm happy with this branch, but there are a couple of fixes that
it needs before it's ready to land:

> 195 + def deriveDistroSeries(
> 196 + self, user, name, distribution=None, displayname=None,
> 197 + title=None, summary=None, description=None, version=None,
> 198 + status=SeriesStatus.FROZEN, architectures=(), packagesets=(),
> 199 + rebuild=False):

Minor nitpick: When defining methods (as opposed to calling them) we
wrap their arguments thus so that the difference between method
declaration and code is clearer:

    def deriveDistroSeries(self, user, name, distribution=None,
                           displayname=None, title=None, summary=None,
                           description=None, version=None,
                           status=SeriesStatus.FROZEN, architectures=(),
                           packagesets=(), rebuild=False):

> 210 + for param in (
> 211 + displayname, title, summary, description, version):
> 212 + if param is None or len(param) == 0:
> 213 + raise DerivationError(
> 214 + "Display Name, Title, Summary, Description and"
> 215 + " Version all need to be set when creating a"
> 216 + " distroseries")

Why bother to do this at all? Why not just put all of these things
earlier in the parameter list and make them required rather than
optional? This validation code is quite confusing; it took me > 5
seconds to work out what was going on and if a DerivationError gets
raised it doesn't actually tell you which parameter you forgot to pass.
Might as well let Python do the work for you.

> 351 + login('<email address hidden>')

This (and all subsequent lines that do the same thing) should use the
ADMIN_EMAIL constant from lp.testing.sampledata rather than having the
email address hardcoded.

review: Needs Fixing (code)
Revision history for this message
Steve Kowalik (stevenk) wrote :

Hi Graham,

The reason I'm checking for those arguments is because the API call can be used one of two ways. Firstly, it can create the distroseries for the caller, and secondly, it can use the distroseries that already exists, so we only need to check if arguments are set if we are creating a distroseries.

Revision history for this message
Graham Binns (gmb) wrote :

On 12 October 2010 11:43, Steve Kowalik <email address hidden> wrote:
> Hi Graham,
>
> The reason I'm checking for those arguments is because the API call can be used one of two ways. Firstly, it can create the distroseries for the caller, and secondly, it can use the distroseries that already exists, so we only need to check if arguments are set if we are creating a distroseries.

Okay, I see your reasoning. However, I think that the code still needs
clarification. I'd rather see:

  if not displayname:
      raise DerivationError(...)
  if not title:
      raise DerivationError(...)
...

Etc. That way it's clearer upon reading what's going on.

Revision history for this message
Graham Binns (gmb) wrote :

Final nitpick, otherwise r=me:

> 210 + if displayname is None or len(displayname) == 0:

Why not just use `if not displayname`? (Lather, rinse, repeat for other conditions in this method).

> 211 + raise DerivationError("Display Name needs to be set when"
> 212 + " creating a distroseries.")

When you have to wrap a call, always start wrapping after the opening parenthesis:

    raise DerivationError(
        "Display Name needs to be set when"
        " creating a distroseries.")

(And if the string will fit on one line thereafter, make it so.)

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'cronscripts/garbo-daily.py'
--- cronscripts/garbo-daily.py 2010-04-27 19:48:39 +0000
+++ cronscripts/garbo-daily.py 2010-10-18 06:17:50 +0000
@@ -13,7 +13,7 @@
13__all__ = []13__all__ = []
1414
15import _pythonpath15import _pythonpath
16from canonical.launchpad.scripts.garbo import DailyDatabaseGarbageCollector16from lp.scripts.garbo import DailyDatabaseGarbageCollector
1717
18if __name__ == '__main__':18if __name__ == '__main__':
19 script = DailyDatabaseGarbageCollector()19 script = DailyDatabaseGarbageCollector()
2020
=== modified file 'cronscripts/garbo-hourly.py'
--- cronscripts/garbo-hourly.py 2010-04-27 19:48:39 +0000
+++ cronscripts/garbo-hourly.py 2010-10-18 06:17:50 +0000
@@ -13,7 +13,7 @@
13__all__ = []13__all__ = []
1414
15import _pythonpath15import _pythonpath
16from canonical.launchpad.scripts.garbo import HourlyDatabaseGarbageCollector16from lp.scripts.garbo import HourlyDatabaseGarbageCollector
1717
18if __name__ == '__main__':18if __name__ == '__main__':
19 script = HourlyDatabaseGarbageCollector()19 script = HourlyDatabaseGarbageCollector()
2020
=== modified file 'lib/canonical/config/tests/test_database_config.py'
--- lib/canonical/config/tests/test_database_config.py 2010-01-13 20:06:09 +0000
+++ lib/canonical/config/tests/test_database_config.py 2010-10-18 06:17:50 +0000
@@ -3,17 +3,20 @@
33
4__metaclass__ = type4__metaclass__ = type
55
6from lp.testing import TestCase
7
8from canonical.config import config, dbconfig6from canonical.config import config, dbconfig
9
10from canonical.launchpad.readonly import read_only_file_exists7from canonical.launchpad.readonly import read_only_file_exists
11from canonical.launchpad.tests.readonly import (8from canonical.launchpad.tests.readonly import (
12 remove_read_only_file, touch_read_only_file)9 remove_read_only_file,
10 touch_read_only_file,
11 )
12from canonical.testing.layers import DatabaseLayer
13from lp.testing import TestCase
1314
1415
15class TestDatabaseConfig(TestCase):16class TestDatabaseConfig(TestCase):
1617
18 layer = DatabaseLayer
19
17 def test_overlay(self):20 def test_overlay(self):
18 # The dbconfig option overlays the database configurations of a21 # The dbconfig option overlays the database configurations of a
19 # chosen config section over the base section.22 # chosen config section over the base section.
@@ -25,11 +28,12 @@
25 self.assertEquals('librarian', config.librarian.dbuser)28 self.assertEquals('librarian', config.librarian.dbuser)
2629
27 dbconfig.setConfigSection('librarian')30 dbconfig.setConfigSection('librarian')
28 self.assertEquals('dbname=launchpad_ftest', dbconfig.rw_main_master)31 expected_db = 'dbname=%s' % DatabaseLayer._db_fixture.dbname
32 self.assertEquals(expected_db, dbconfig.rw_main_master)
29 self.assertEquals('librarian', dbconfig.dbuser)33 self.assertEquals('librarian', dbconfig.dbuser)
3034
31 dbconfig.setConfigSection('launchpad')35 dbconfig.setConfigSection('launchpad')
32 self.assertEquals('dbname=launchpad_ftest', dbconfig.rw_main_master)36 self.assertEquals(expected_db, dbconfig.rw_main_master)
33 self.assertEquals('launchpad_main', dbconfig.dbuser)37 self.assertEquals('launchpad_main', dbconfig.dbuser)
3438
35 def test_required_values(self):39 def test_required_values(self):
3640
=== modified file 'lib/canonical/database/ftests/test_postgresql.py'
--- lib/canonical/database/ftests/test_postgresql.py 2010-07-14 14:11:15 +0000
+++ lib/canonical/database/ftests/test_postgresql.py 2010-10-18 06:17:50 +0000
@@ -10,8 +10,9 @@
10def setUp(test):10def setUp(test):
1111
12 # Build a fresh, empty database and connect12 # Build a fresh, empty database and connect
13 PgTestSetup().setUp()13 test._db_fixture = PgTestSetup()
14 con = PgTestSetup().connect()14 test._db_fixture.setUp()
15 con = test._db_fixture.connect()
1516
16 # Create a test schema demonstrating the edge cases17 # Create a test schema demonstrating the edge cases
17 cur = con.cursor()18 cur = con.cursor()
@@ -53,8 +54,9 @@
53 test.globs['cur'] = cur54 test.globs['cur'] = cur
5455
55def tearDown(test):56def tearDown(test):
56 PgTestSetup().tearDown()
57 test.globs['con'].close()57 test.globs['con'].close()
58 test._db_fixture.tearDown()
59 del test._db_fixture
5860
59def test_suite():61def test_suite():
60 suite = DocTestSuite(62 suite = DocTestSuite(
6163
=== modified file 'lib/canonical/database/ftests/test_sqlbaseconnect.txt'
--- lib/canonical/database/ftests/test_sqlbaseconnect.txt 2009-04-17 10:32:16 +0000
+++ lib/canonical/database/ftests/test_sqlbaseconnect.txt 2010-10-18 06:17:50 +0000
@@ -19,7 +19,7 @@
19Specifying the user connects as that user.19Specifying the user connects as that user.
2020
21 >>> do_connect(user=config.launchpad_session.dbuser)21 >>> do_connect(user=config.launchpad_session.dbuser)
22 Connected as session to launchpad_ftest in read committed isolation.22 Connected as session to ... in read committed isolation.
2323
24Specifying the database name connects to that database.24Specifying the database name connects to that database.
2525
@@ -31,5 +31,5 @@
31 >>> do_connect(31 >>> do_connect(
32 ... user=config.launchpad.dbuser,32 ... user=config.launchpad.dbuser,
33 ... isolation=ISOLATION_LEVEL_SERIALIZABLE)33 ... isolation=ISOLATION_LEVEL_SERIALIZABLE)
34 Connected as launchpad_main to launchpad_ftest in serializable isolation.34 Connected as launchpad_main to ... in serializable isolation.
3535
3636
=== modified file 'lib/canonical/ftests/pgsql.py'
--- lib/canonical/ftests/pgsql.py 2009-10-09 04:05:34 +0000
+++ lib/canonical/ftests/pgsql.py 2010-10-18 06:17:50 +0000
@@ -7,7 +7,7 @@
77
8__metaclass__ = type8__metaclass__ = type
99
10import unittest10import os
11import time11import time
1212
13import psycopg213import psycopg2
@@ -119,7 +119,6 @@
119119
120_org_connect = None120_org_connect = None
121def fake_connect(*args, **kw):121def fake_connect(*args, **kw):
122 global _org_connect
123 return ConnectionWrapper(_org_connect(*args, **kw))122 return ConnectionWrapper(_org_connect(*args, **kw))
124123
125def installFakeConnect():124def installFakeConnect():
@@ -136,9 +135,13 @@
136135
137136
138class PgTestSetup:137class PgTestSetup:
138
139 connections = [] # Shared139 connections = [] # Shared
140 # Use a dynamically generated dbname:
141 dynamic = object()
140142
141 template = 'template1'143 template = 'template1'
144 # Needs to match configs/testrunner*/*:
142 dbname = 'launchpad_ftest'145 dbname = 'launchpad_ftest'
143 dbuser = None146 dbuser = None
144 host = None147 host = None
@@ -165,8 +168,13 @@
165 '''168 '''
166 if template is not None:169 if template is not None:
167 self.template = template170 self.template = template
168 if dbname is not None:171 if dbname is PgTestSetup.dynamic:
172 self.dbname = self.__class__.dbname + "_" + str(os.getpid())
173 elif dbname is not None:
169 self.dbname = dbname174 self.dbname = dbname
175 else:
176 # Fallback to the class name.
177 self.dbname = self.__class__.dbname
170 if dbuser is not None:178 if dbuser is not None:
171 self.dbuser = dbuser179 self.dbuser = dbuser
172 if host is not None:180 if host is not None:
@@ -331,30 +339,3 @@
331 as database changes made from a subprocess.339 as database changes made from a subprocess.
332 """340 """
333 PgTestSetup._reset_db = True341 PgTestSetup._reset_db = True
334
335
336class PgTestCase(unittest.TestCase):
337 dbname = None
338 dbuser = None
339 host = None
340 port = None
341 template = None
342 def setUp(self):
343 pg_test_setup = PgTestSetup(
344 self.template, self.dbname, self.dbuser, self.host, self.port
345 )
346 pg_test_setup.setUp()
347 self.dbname = pg_test_setup.dbname
348 self.dbuser = pg_test_setup.dbuser
349 assert self.dbname, 'self.dbname is not set.'
350
351 def tearDown(self):
352 PgTestSetup(
353 self.template, self.dbname, self.dbuser, self.host, self.port
354 ).tearDown()
355
356 def connect(self):
357 return PgTestSetup(
358 self.template, self.dbname, self.dbuser, self.host, self.port
359 ).connect()
360
361342
=== modified file 'lib/canonical/ftests/test_pgsql.py'
--- lib/canonical/ftests/test_pgsql.py 2009-06-25 05:30:52 +0000
+++ lib/canonical/ftests/test_pgsql.py 2010-10-18 06:17:50 +0000
@@ -1,77 +1,84 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4import unittest4import os
5from canonical.ftests.pgsql import PgTestCase, PgTestSetup, ConnectionWrapper5
66import testtools
77
8class TestPgTestCase(PgTestCase):8from canonical.ftests.pgsql import (
99 ConnectionWrapper,
10 def testRollback(self):10 PgTestSetup,
11 # This test creates a table. We run the same test twice,11 )
12 # which will fail if database changes are not rolled back12
13 con = self.connect()13
14 cur = con.cursor()14class TestPgTestSetup(testtools.TestCase):
15 cur.execute('CREATE TABLE foo (x int)')15
16 cur.execute('INSERT INTO foo VALUES (1)')16 def test_db_naming(self):
17 cur.execute('SELECT x FROM foo')17 fixture = PgTestSetup(dbname=PgTestSetup.dynamic)
18 res = list(cur.fetchall())18 expected_name = "%s_%s" % (PgTestSetup.dbname, os.getpid())
19 self.failUnless(len(res) == 1)19 self.assertEqual(expected_name, fixture.dbname)
20 self.failUnless(res[0][0] == 1)20 fixture.setUp()
21 con.commit()21 self.addCleanup(fixture.dropDb)
2222 self.addCleanup(fixture.tearDown)
23 testRollback2 = testRollback23 cur = fixture.connect().cursor()
2424 cur.execute('SELECT current_database()')
25class TestOptimization(unittest.TestCase):25 where = cur.fetchone()[0]
26 self.assertEqual(expected_name, where)
27
26 def testOptimization(self):28 def testOptimization(self):
27 # Test to ensure that the database is destroyed only when necessary29 # Test to ensure that the database is destroyed only when necessary
2830
29 # Make a change to a database31 # Make a change to a database
30 PgTestSetup().setUp()32 fixture = PgTestSetup()
33 fixture.setUp()
31 try:34 try:
32 con = PgTestSetup().connect()35 con = fixture.connect()
33 cur = con.cursor()36 cur = con.cursor()
34 cur.execute('CREATE TABLE foo (x int)')37 cur.execute('CREATE TABLE foo (x int)')
35 con.commit()38 con.commit()
36 # Fake it so the harness doesn't know a change has been made39 # Fake it so the harness doesn't know a change has been made
37 ConnectionWrapper.committed = False40 ConnectionWrapper.committed = False
38 finally:41 finally:
39 PgTestSetup().tearDown()42 fixture.tearDown()
4043
41 # Now check to ensure that the table we just created is still there44 # Now check to ensure that the table we just created is still there if
42 PgTestSetup().setUp()45 # we reuse the fixture.
46 fixture.setUp()
43 try:47 try:
44 con = PgTestSetup().connect()48 con = fixture.connect()
45 cur = con.cursor()49 cur = con.cursor()
46 # This tests that the table still exists, as well as modifying the50 # This tests that the table still exists, as well as modifying the
47 # db51 # db
48 cur.execute('INSERT INTO foo VALUES (1)')52 cur.execute('INSERT INTO foo VALUES (1)')
49 con.commit()53 con.commit()
50 finally:54 finally:
51 PgTestSetup().tearDown()55 fixture.tearDown()
5256
53 # Now ensure that the table is gone57 # Now ensure that the table is gone - the commit must have been rolled
54 PgTestSetup().setUp()58 # back.
59 fixture.setUp()
55 try:60 try:
56 con = PgTestSetup().connect()61 con = fixture.connect()
57 cur = con.cursor()62 cur = con.cursor()
58 cur.execute('CREATE TABLE foo (x int)')63 cur.execute('CREATE TABLE foo (x int)')
59 con.commit()64 con.commit()
60 ConnectionWrapper.committed = False # Leave the table65 ConnectionWrapper.committed = False # Leave the table
61 finally:66 finally:
62 PgTestSetup().tearDown()67 fixture.tearDown()
6368
64 # The database should *always* be recreated if the template69 # The database should *always* be recreated if a new template had been
65 # changes.70 # chosen.
66 PgTestSetup._last_db = ('whatever', 'launchpad_ftest')71 PgTestSetup._last_db = ('different-template', fixture.dbname)
67 PgTestSetup().setUp()72 fixture.setUp()
68 try:73 try:
69 con = PgTestSetup().connect()74 con = fixture.connect()
70 cur = con.cursor()75 cur = con.cursor()
76 # If this fails, TABLE foo still existed and the DB wasn't rebuilt
77 # correctly.
71 cur.execute('CREATE TABLE foo (x int)')78 cur.execute('CREATE TABLE foo (x int)')
72 con.commit()79 con.commit()
73 finally:80 finally:
74 PgTestSetup().tearDown()81 fixture.tearDown()
7582
76 def test_sequences(self):83 def test_sequences(self):
77 # Sequences may be affected by connections even if the connection84 # Sequences may be affected by connections even if the connection
@@ -80,9 +87,10 @@
80 # the sequences.87 # the sequences.
8188
82 # Setup a table that uses a sequence89 # Setup a table that uses a sequence
83 PgTestSetup().setUp()90 fixture = PgTestSetup()
91 fixture.setUp()
84 try:92 try:
85 con = PgTestSetup().connect()93 con = fixture.connect()
86 cur = con.cursor()94 cur = con.cursor()
87 cur.execute('CREATE TABLE foo (x serial, y integer)')95 cur.execute('CREATE TABLE foo (x serial, y integer)')
88 con.commit()96 con.commit()
@@ -90,15 +98,15 @@
90 # Fake it so the harness doesn't know a change has been made98 # Fake it so the harness doesn't know a change has been made
91 ConnectionWrapper.committed = False99 ConnectionWrapper.committed = False
92 finally:100 finally:
93 PgTestSetup().tearDown()101 fixture.tearDown()
94102
95 sequence_values = []103 sequence_values = []
96 # Insert a row into it and roll back the changes. Each time, we104 # Insert a row into it and roll back the changes. Each time, we
97 # should end up with the same sequence value105 # should end up with the same sequence value
98 for i in range(3):106 for i in range(3):
99 PgTestSetup().setUp()107 fixture.setUp()
100 try:108 try:
101 con = PgTestSetup().connect()109 con = fixture.connect()
102 cur = con.cursor()110 cur = con.cursor()
103 cur.execute('INSERT INTO foo (y) VALUES (1)')111 cur.execute('INSERT INTO foo (y) VALUES (1)')
104 cur.execute("SELECT currval('foo_x_seq')")112 cur.execute("SELECT currval('foo_x_seq')")
@@ -106,7 +114,7 @@
106 con.rollback()114 con.rollback()
107 con.close()115 con.close()
108 finally:116 finally:
109 PgTestSetup().tearDown()117 fixture.tearDown()
110118
111 # Fail if we got a diffent sequence value at some point119 # Fail if we got a diffent sequence value at some point
112 for v in sequence_values:120 for v in sequence_values:
@@ -114,9 +122,9 @@
114122
115 # Repeat the test, but this time with some data already in the123 # Repeat the test, but this time with some data already in the
116 # table124 # table
117 PgTestSetup().setUp()125 fixture.setUp()
118 try:126 try:
119 con = PgTestSetup().connect()127 con = fixture.connect()
120 cur = con.cursor()128 cur = con.cursor()
121 cur.execute('INSERT INTO foo (y) VALUES (1)')129 cur.execute('INSERT INTO foo (y) VALUES (1)')
122 con.commit()130 con.commit()
@@ -124,15 +132,15 @@
124 # Fake it so the harness doesn't know a change has been made132 # Fake it so the harness doesn't know a change has been made
125 ConnectionWrapper.committed = False133 ConnectionWrapper.committed = False
126 finally:134 finally:
127 PgTestSetup().tearDown()135 fixture.tearDown()
128136
129 sequence_values = []137 sequence_values = []
130 # Insert a row into it and roll back the changes. Each time, we138 # Insert a row into it and roll back the changes. Each time, we
131 # should end up with the same sequence value139 # should end up with the same sequence value
132 for i in range(1,3):140 for i in range(1,3):
133 PgTestSetup().setUp()141 fixture.setUp()
134 try:142 try:
135 con = PgTestSetup().connect()143 con = fixture.connect()
136 cur = con.cursor()144 cur = con.cursor()
137 cur.execute('INSERT INTO foo (y) VALUES (1)')145 cur.execute('INSERT INTO foo (y) VALUES (1)')
138 cur.execute("SELECT currval('foo_x_seq')")146 cur.execute("SELECT currval('foo_x_seq')")
@@ -140,19 +148,8 @@
140 con.rollback()148 con.rollback()
141 con.close()149 con.close()
142 finally:150 finally:
143 PgTestSetup().tearDown()151 fixture.tearDown()
144152
145 # Fail if we got a diffent sequence value at some point153 # Fail if we got a diffent sequence value at some point
146 for v in sequence_values:154 for v in sequence_values:
147 self.failUnlessEqual(v, sequence_values[0])155 self.failUnlessEqual(v, sequence_values[0])
148
149
150def test_suite():
151 suite = unittest.TestSuite()
152 suite.addTest(unittest.makeSuite(TestPgTestCase))
153 suite.addTest(unittest.makeSuite(TestOptimization))
154 return suite
155
156if __name__ == '__main__':
157 unittest.main()
158
159156
=== modified file 'lib/canonical/launchpad/doc/canonical-config.txt'
--- lib/canonical/launchpad/doc/canonical-config.txt 2010-01-05 19:09:58 +0000
+++ lib/canonical/launchpad/doc/canonical-config.txt 2010-10-18 06:17:50 +0000
@@ -14,8 +14,10 @@
14simple configuration).14simple configuration).
1515
16 >>> from canonical.config import config16 >>> from canonical.config import config
17 >>> print config.database.rw_main_master17 >>> from canonical.testing.layers import DatabaseLayer
18 dbname=launchpad_ftest18 >>> expected = 'dbname=%s' % DatabaseLayer._db_fixture.dbname
19 >>> expected == config.database.rw_main_master
20 True
19 >>> config.database.db_statement_timeout is None21 >>> config.database.db_statement_timeout is None
20 True22 True
21 >>> config.launchpad.dbuser23 >>> config.launchpad.dbuser
@@ -226,7 +228,7 @@
226# >>> canonical.config.config = config228# >>> canonical.config.config = config
227# >>> config.filename229# >>> config.filename
228# '.../configs/testrunner/launchpad-lazr.conf'230# '.../configs/testrunner/launchpad-lazr.conf'
229# >>> config.dbname231# >>> config.dbname == DatabaseLayer._db_fixture.dbname
230# 'launchpad_ftest'232# True
231# >>> config._cache.testrunner233# >>> config._cache.testrunner
232# <SectionValue for canonical 'testrunner'>234# <SectionValue for canonical 'testrunner'>
233235
=== modified file 'lib/canonical/launchpad/doc/old-testing.txt'
--- lib/canonical/launchpad/doc/old-testing.txt 2010-10-03 20:23:37 +0000
+++ lib/canonical/launchpad/doc/old-testing.txt 2010-10-18 06:17:50 +0000
@@ -18,11 +18,6 @@
18zope, we should not be testing it with the full Z3 functional test18zope, we should not be testing it with the full Z3 functional test
19harness).19harness).
2020
21If you are wondering why we use `PgTestSetup().setUp()` and
22`PgTestSetup.tearDown()` instead of `pgtestsetup.setUp()` or
23`pgtestsetup.tearDown()`, it is because I'm mirroring the design used in
24Zope3's `FunctionalTestSetup`.
25
26canonical.functional.FunctionalTestCase21canonical.functional.FunctionalTestCase
27---------------------------------------22---------------------------------------
2823
@@ -42,11 +37,12 @@
4237
43The setup procedure builds us a fresh, empty database38The setup procedure builds us a fresh, empty database
4439
45>>> PgTestSetup().setUp()40>>> fixture = PgTestSetup()
41>>> fixture.setUp()
4642
47We can get connections to this database43We can get connections to this database
4844
49>>> connection = PgTestSetup().connect()45>>> connection = fixture.connect()
50>>> cursor = connection.cursor()46>>> cursor = connection.cursor()
51>>> cursor.execute("""CREATE TABLE Beer (47>>> cursor.execute("""CREATE TABLE Beer (
52... id serial PRIMARY KEY, name text, stamp timestamp without time zone48... id serial PRIMARY KEY, name text, stamp timestamp without time zone
@@ -68,28 +64,29 @@
68When we have finished, we need to call the tearDown method which closes64When we have finished, we need to call the tearDown method which closes
69all outstanding connections and destroys the database65all outstanding connections and destroys the database
7066
71>>> PgTestSetup().tearDown()67>>> fixture.tearDown()
7268
73Because the database has been destroyed, further tests will not be69Because the database has been destroyed, further tests will not be
74affected.70affected.
7571
76>>> PgTestSetup().setUp()72>>> fixture.setUp()
77>>> connection = PgTestSetup().connect()73>>> connection = fixture.connect()
78>>> cursor = connection.cursor()74>>> cursor = connection.cursor()
79>>> cursor.execute("CREATE TABLE Beer (id serial PRIMARY KEY, name text)")75>>> cursor.execute("CREATE TABLE Beer (id serial PRIMARY KEY, name text)")
80>>> PgTestSetup().tearDown()76>>> fixture.tearDown()
8177
82We can also specify a different template to duplicate than the default78We can also specify a different template to duplicate than the default
83clean one (template1). For example, if you need a launchpad database79clean one (template1). For example, if you need a launchpad database
84containing no data, you can use `launchpad_empty` as the template.80containing no data, you can use `launchpad_empty` as the template.
8581
86>>> PgTestSetup('launchpad_empty').setUp()82>>> fixture = PgTestSetup('launchpad_empty')
87>>> connection = PgTestSetup().connect()83>>> fixture.setUp()
84>>> connection = fixture.connect()
88>>> cursor = connection.cursor()85>>> cursor = connection.cursor()
89>>> cursor.execute("SELECT COUNT(*) FROM Person")86>>> cursor.execute("SELECT COUNT(*) FROM Person")
90>>> int(cursor.fetchone()[0])87>>> int(cursor.fetchone()[0])
910880
92>>> PgTestSetup().tearDown()89>>> fixture.tearDown()
9390
94We can also specify the user that we connect as to avoid connecting as the91We can also specify the user that we connect as to avoid connecting as the
95PostgreSQL default user.92PostgreSQL default user.
@@ -108,14 +105,12 @@
108------------------105------------------
109106
110LaunchpadTestSetup is identical to PgTestSetup, except that it creates a107LaunchpadTestSetup is identical to PgTestSetup, except that it creates a
111fresh copy of the Launchpad database filled with our sample data. This108fresh copy of the Launchpad database filled with our sample data.
112class is defined in canonical.launchpad.ftests.harness.109
113110>>> from canonical.testing.layers import LaunchpadTestSetup
114Note that at this level, you cannot access any of the SQLBase objects111>>> fixture = LaunchpadTestSetup()
115112>>> fixture.setUp()
116>>> from canonical.launchpad.ftests.harness import LaunchpadTestSetup113>>> connection = fixture.connect()
117>>> LaunchpadTestSetup().setUp()
118>>> connection = LaunchpadTestSetup().connect()
119>>> cursor = connection.cursor()114>>> cursor = connection.cursor()
120>>> cursor.execute("SELECT displayname FROM person WHERE name='carlos'")115>>> cursor.execute("SELECT displayname FROM person WHERE name='carlos'")
121>>> cursor.fetchone()[0]116>>> cursor.fetchone()[0]
@@ -127,7 +122,7 @@
127>>> cursor.fetchone()[0]122>>> cursor.fetchone()[0]
128u'launchpad'123u'launchpad'
129124
130>>> LaunchpadTestSetup().tearDown()125>>> fixture.tearDown()
131126
132You can connect as a different database user using the same mechanism127You can connect as a different database user using the same mechanism
133described above for PgTestSetup128described above for PgTestSetup
@@ -143,114 +138,13 @@
143>>> lpsetup.tearDown()138>>> lpsetup.tearDown()
144139
145140
146LaunchpadZopelessTestSetup
147--------------------------
148
149LaunchpadZopelessTestSetup builds on LaunchpadTestSetup, calling
150initZopeless for you so you can access the SQLBase objects without needing
151the Zope3 infrastructure.
152
153>>> from canonical.launchpad.ftests.harness import LaunchpadZopelessTestSetup
154>>> LaunchpadZopelessTestSetup().setUp()
155>>> from lp.registry.model.person import Person
156>>> stub = Person.byName('stub')
157>>> stub.displayname
158u'Stuart Bishop'
159>>> stub.displayname = u'The Walrus'
160>>> stub.displayname
161u'The Walrus'
162
163You have access to the zopeless transaction
164
165>>> LaunchpadZopelessTestSetup().txn.abort()
166>>> stub.displayname
167u'Stuart Bishop'
168
169And always remember to tearDown or you will victimize other tests!
170
171>>> LaunchpadZopelessTestSetup().tearDown()
172
173
174In general, Zopeless tests should never be running as the launchpad user.
175You can select the user you connect as:
176
177>>> setup = LaunchpadZopelessTestSetup(dbuser=config.librarian.dbuser)
178>>> setup.setUp()
179>>> from lp.registry.model.sourcepackagename import SourcePackageName
180>>> SourcePackageName.get(1).name
181Traceback (most recent call last):
182...
183ProgrammingError: permission denied for relation sourcepackagename
184<BLANKLINE>
185>>> setup.tearDown()
186
187
188LaunchpadFunctionalTestSetup
189----------------------------
190
191One with the lot. A LaunchpadTestSetup which also loads in the Zope3
192environment.
193
194>>> from canonical.launchpad.ftests.harness import LaunchpadFunctionalTestSetup
195>>> LaunchpadFunctionalTestSetup().setUp()
196
197You have full access to the SQLBase objects
198
199>>> mark = Person.byName('mark')
200>>> mark.displayname
201u'Mark Shuttleworth'
202
203You also have access to the Zope3 component architecture, as registered
204by ftesting.zcml
205
206>>> from zope.app import zapi
207>>> from zope.sendmail.interfaces import IMailer
208>>> zapi.getUtility(IMailer, 'smtp') is not None
209True
210
211>>> LaunchpadFunctionalTestSetup().tearDown()
212
213You can change the user that the tests connect as:
214
215 XXX 2008-05-29 jamesh:
216 Using LaunchpadFunctionalLayer for non-webapp db users is generally
217 a sign of a bug. These bits of code should generally be using
218 LaunchpadZopelessLayer.
219
220##>>> setup = LaunchpadFunctionalTestSetup(dbuser=config.librarian.dbuser)
221##>>> setup.setUp()
222##>>> connection = setup.connect()
223##>>> cursor = connection.cursor()
224##>>> cursor.execute('SELECT current_user')
225##>>> cursor.fetchone()[0]
226##u'librarian'
227##>>> SourcePackageName.get(1).name
228##Traceback (most recent call last):
229##...
230##ProgrammingError: permission denied ...
231##>>> setup.tearDown()
232
233And the next test will be unaffected:
234
235>>> setup = LaunchpadFunctionalTestSetup()
236>>> setup.setUp()
237>>> connection = setup.connect()
238>>> cursor = connection.cursor()
239>>> cursor.execute('SELECT current_user')
240>>> cursor.fetchone()[0]
241u'launchpad'
242>>> SourcePackageName.get(1).name
243u'mozilla-firefox'
244>>> setup.tearDown()
245
246
247LibrarianTestSetup141LibrarianTestSetup
248------------------142------------------
249143
250Code that needs to access the Librarian can do so easily. Note that144Code that needs to access the Librarian can do so easily. Note that
251LibrarianTestSetup requires the Launchpad database to be available, and145LibrarianTestSetup requires the Launchpad database to be available, and
252thus requires LaunchpadTestSetup or similar to be used in tandam.146thus requires LaunchpadTestSetup or similar to be used in tandam.
253You probably really want LaunchpadFunctionalTestSetup so you can access147You probably really want LaunchpadFunctionalLayer so you can access
254the Librarian as a Utility.148the Librarian as a Utility.
255149
256>>> from canonical.librarian.testing.server import LibrarianTestSetup150>>> from canonical.librarian.testing.server import LibrarianTestSetup
@@ -259,7 +153,6 @@
259>>> from canonical.librarian.interfaces import ILibrarianClient153>>> from canonical.librarian.interfaces import ILibrarianClient
260>>> from StringIO import StringIO154>>> from StringIO import StringIO
261155
262>>> LaunchpadFunctionalTestSetup().setUp()
263>>> librarian = LibrarianTestSetup()156>>> librarian = LibrarianTestSetup()
264>>> librarian.setUp()157>>> librarian.setUp()
265>>> login(ANONYMOUS)158>>> login(ANONYMOUS)
@@ -285,7 +178,6 @@
285True178True
286179
287>>> librarian.tearDown()180>>> librarian.tearDown()
288>>> LaunchpadFunctionalTestSetup().tearDown()
289181
290>>> from canonical.testing import reset_logging182>>> from canonical.testing import reset_logging
291>>> reset_logging()183>>> reset_logging()
292184
=== modified file 'lib/canonical/launchpad/doc/security-proxies.txt'
--- lib/canonical/launchpad/doc/security-proxies.txt 2010-10-09 16:36:22 +0000
+++ lib/canonical/launchpad/doc/security-proxies.txt 2010-10-18 06:17:50 +0000
@@ -6,13 +6,10 @@
66
7First, some imports and set up::7First, some imports and set up::
88
9 >>> from canonical.launchpad.ftests.harness import LaunchpadFunctionalTestSetup
10 >>> from zope.component import getUtility9 >>> from zope.component import getUtility
11 >>> from lp.registry.interfaces.person import IPersonSet10 >>> from lp.registry.interfaces.person import IPersonSet
12 >>> from lp.registry.model.person import Person11 >>> from lp.registry.model.person import Person
1312
14 >>> LaunchpadFunctionalTestSetup().setUp()
15
16Get a proxied and unproxied person object for the same person, and demonstrate13Get a proxied and unproxied person object for the same person, and demonstrate
17working comparisons::14working comparisons::
1815
@@ -57,8 +54,3 @@
57 True54 True
58 >>> hoary.status is SeriesStatus.DEVELOPMENT55 >>> hoary.status is SeriesStatus.DEVELOPMENT
59 False56 False
60
61Finally, tear down the test:
62
63 >>> LaunchpadFunctionalTestSetup().tearDown()
64
6557
=== removed file 'lib/canonical/launchpad/ftests/harness.py'
--- lib/canonical/launchpad/ftests/harness.py 2010-10-04 19:50:45 +0000
+++ lib/canonical/launchpad/ftests/harness.py 1970-01-01 00:00:00 +0000
@@ -1,84 +0,0 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""
5Launchpad functional test helpers.
6
7This file needs to be refactored, moving its functionality into
8canonical.testing
9"""
10
11__metaclass__ = type
12
13
14from zope.app.testing.functional import FunctionalTestSetup
15
16from canonical.database.sqlbase import ZopelessTransactionManager
17from canonical.ftests.pgsql import PgTestSetup
18from canonical.lp import initZopeless
19from canonical.testing.layers import (
20 FunctionalLayer,
21 ZopelessLayer,
22 )
23from canonical.testing.layers import (
24 disconnect_stores,
25 reconnect_stores,
26 )
27
28
29__all__ = [
30 'LaunchpadTestSetup', 'LaunchpadZopelessTestSetup',
31 'LaunchpadFunctionalTestSetup',
32 ]
33
34
35class LaunchpadTestSetup(PgTestSetup):
36 template = 'launchpad_ftest_template'
37 dbname = 'launchpad_ftest' # Needs to match ftesting.zcml
38 dbuser = 'launchpad'
39
40
41class LaunchpadZopelessTestSetup(LaunchpadTestSetup):
42 txn = ZopelessTransactionManager
43 def setUp(self, dbuser=None):
44 assert ZopelessTransactionManager._installed is None, \
45 'Last test using Zopeless failed to tearDown correctly'
46 super(LaunchpadZopelessTestSetup, self).setUp()
47 if self.host is not None:
48 raise NotImplementedError('host not supported yet')
49 if self.port is not None:
50 raise NotImplementedError('port not supported yet')
51 if dbuser is not None:
52 self.dbuser = dbuser
53 initZopeless(dbname=self.dbname, dbuser=self.dbuser)
54
55 def tearDown(self):
56 LaunchpadZopelessTestSetup.txn.uninstall()
57 assert ZopelessTransactionManager._installed is None, \
58 'Failed to tearDown Zopeless correctly'
59
60
61class LaunchpadFunctionalTestSetup(LaunchpadTestSetup):
62 def _checkLayerInvariants(self):
63 assert FunctionalLayer.isSetUp or ZopelessLayer.isSetUp, """
64 FunctionalTestSetup invoked at an inappropriate time.
65 May only be invoked in the FunctionalLayer or ZopelessLayer
66 """
67
68 def setUp(self, dbuser=None):
69 self._checkLayerInvariants()
70 if dbuser is not None:
71 self.dbuser = dbuser
72 assert self.dbuser == 'launchpad', (
73 "Non-default user names should probably be using "
74 "script layer or zopeless layer.")
75 disconnect_stores()
76 super(LaunchpadFunctionalTestSetup, self).setUp()
77 FunctionalTestSetup().setUp()
78 reconnect_stores()
79
80 def tearDown(self):
81 self._checkLayerInvariants()
82 FunctionalTestSetup().tearDown()
83 disconnect_stores()
84 super(LaunchpadFunctionalTestSetup, self).tearDown()
850
=== modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
--- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-10-05 08:17:29 +0000
+++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-10-18 06:17:50 +0000
@@ -371,6 +371,8 @@
371patch_collection_return_type(371patch_collection_return_type(
372 IDistroSeries, 'getPackageUploads', IPackageUpload)372 IDistroSeries, 'getPackageUploads', IPackageUpload)
373patch_reference_property(IDistroSeries, 'parent_series', IDistroSeries)373patch_reference_property(IDistroSeries, 'parent_series', IDistroSeries)
374patch_plain_parameter_type(
375 IDistroSeries, 'deriveDistroSeries', 'distribution', IDistroSeries)
374376
375# IDistroSeriesDifferenceComment377# IDistroSeriesDifferenceComment
376IDistroSeriesDifferenceComment['comment_author'].schema = IPerson378IDistroSeriesDifferenceComment['comment_author'].schema = IPerson
377379
=== modified file 'lib/canonical/launchpad/pagetests/standalone/xx-dbpolicy.txt'
--- lib/canonical/launchpad/pagetests/standalone/xx-dbpolicy.txt 2010-01-13 13:50:39 +0000
+++ lib/canonical/launchpad/pagetests/standalone/xx-dbpolicy.txt 2010-10-18 06:17:50 +0000
@@ -20,9 +20,11 @@
20 >>> from zope.component import getUtility20 >>> from zope.component import getUtility
21 >>> from canonical.launchpad.webapp.interfaces import (21 >>> from canonical.launchpad.webapp.interfaces import (
22 ... IStoreSelector, MAIN_STORE, MASTER_FLAVOR, SLAVE_FLAVOR)22 ... IStoreSelector, MAIN_STORE, MASTER_FLAVOR, SLAVE_FLAVOR)
23 >>> from canonical.testing.layers import DatabaseLayer
23 >>> master = getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR)24 >>> master = getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR)
24 >>> master.execute("SELECT current_database()").get_one()[0]25 >>> dbname = DatabaseLayer._db_fixture.dbname
25 u'launchpad_ftest'26 >>> dbname == master.execute("SELECT current_database()").get_one()[0]
27 True
26 >>> slave = getUtility(IStoreSelector).get(MAIN_STORE, SLAVE_FLAVOR)28 >>> slave = getUtility(IStoreSelector).get(MAIN_STORE, SLAVE_FLAVOR)
27 >>> slave.execute("SELECT current_database()").get_one()[0]29 >>> slave.execute("SELECT current_database()").get_one()[0]
28 u'launchpad_empty'30 u'launchpad_empty'
@@ -47,7 +49,7 @@
4749
48 >>> def whichdb(browser):50 >>> def whichdb(browser):
49 ... dbname = extract_text(find_tag_by_id(browser.contents, 'dbname'))51 ... dbname = extract_text(find_tag_by_id(browser.contents, 'dbname'))
50 ... if dbname == 'launchpad_ftest':52 ... if dbname == DatabaseLayer._db_fixture.dbname:
51 ... return 'MASTER'53 ... return 'MASTER'
52 ... elif dbname == 'launchpad_empty':54 ... elif dbname == 'launchpad_empty':
53 ... return 'SLAVE'55 ... return 'SLAVE'
5456
=== modified file 'lib/canonical/launchpad/tests/test_sampledata.py'
--- lib/canonical/launchpad/tests/test_sampledata.py 2010-09-22 13:26:50 +0000
+++ lib/canonical/launchpad/tests/test_sampledata.py 2010-10-18 06:17:50 +0000
@@ -12,7 +12,6 @@
12__all__ = []12__all__ = []
1313
14import subprocess14import subprocess
15import unittest
1615
17from canonical.testing.layers import DatabaseLayer16from canonical.testing.layers import DatabaseLayer
18from lp.testing import TestCase17from lp.testing import TestCase
@@ -37,14 +36,11 @@
37 cmd = (36 cmd = (
38 "pg_dump --format=c --compress=0 --no-privileges --no-owner"37 "pg_dump --format=c --compress=0 --no-privileges --no-owner"
39 " --schema=public %s | pg_restore --clean"38 " --schema=public %s | pg_restore --clean"
40 " --exit-on-error --dbname=launchpad_ftest" % source_dbname)39 " --exit-on-error --dbname=%s" % (
40 source_dbname, DatabaseLayer._db_fixture.dbname))
41 proc = subprocess.Popen(41 proc = subprocess.Popen(
42 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,42 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
43 stdin=subprocess.PIPE)43 stdin=subprocess.PIPE)
44 (stdout, stderr) = proc.communicate()44 (stdout, stderr) = proc.communicate()
45 rv = proc.wait()45 rv = proc.wait()
46 self.failUnlessEqual(rv, 0, "Dump/Restore failed: %s" % stdout)46 self.failUnlessEqual(rv, 0, "Dump/Restore failed: %s" % stdout)
47
48
49def test_suite():
50 return unittest.TestLoader().loadTestsFromName(__name__)
5147
=== modified file 'lib/canonical/launchpad/webapp/ftests/test_adapter.txt'
--- lib/canonical/launchpad/webapp/ftests/test_adapter.txt 2010-09-17 00:53:33 +0000
+++ lib/canonical/launchpad/webapp/ftests/test_adapter.txt 2010-10-18 06:17:50 +0000
@@ -18,14 +18,18 @@
18 >>> from canonical.launchpad.webapp.adapter import (18 >>> from canonical.launchpad.webapp.adapter import (
19 ... clear_request_started, get_request_statements,19 ... clear_request_started, get_request_statements,
20 ... set_request_started)20 ... set_request_started)
21 >>> from canonical.testing.layers import DatabaseLayer
21 >>> from lp.services.timeline.requesttimeline import get_request_timeline22 >>> from lp.services.timeline.requesttimeline import get_request_timeline
2223
23There are several possible database connections available via the24There are several possible database connections available via the
24IStoreSelector utility.25IStoreSelector utility.
2526
26 >>> store = getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR)27 >>> store = getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR)
27 >>> print store.execute("SELECT current_database()").get_one()[0]28 >>> dbname = DatabaseLayer._db_fixture.dbname
28 launchpad_ftest29 >>> active_name = store.execute("SELECT current_database()").get_one()[0]
30 >>> if active_name != dbname: print '%s != %s' % (active_name, dbname)
31 >>> active_name == dbname
32 True
2933
3034
31Statement Logging35Statement Logging
3236
=== modified file 'lib/canonical/lp/ftests/test_zopeless.py'
--- lib/canonical/lp/ftests/test_zopeless.py 2010-10-04 19:50:45 +0000
+++ lib/canonical/lp/ftests/test_zopeless.py 2010-10-18 06:17:50 +0000
@@ -14,9 +14,11 @@
14from sqlobject import StringCol, IntCol14from sqlobject import StringCol, IntCol
1515
16from canonical.database.sqlbase import SQLBase, alreadyInstalledMsg, cursor16from canonical.database.sqlbase import SQLBase, alreadyInstalledMsg, cursor
17from canonical.ftests.pgsql import PgTestSetup
18from canonical.lp import initZopeless17from canonical.lp import initZopeless
19from canonical.testing.layers import LaunchpadScriptLayer18from canonical.testing.layers import (
19 DatabaseLayer,
20 LaunchpadScriptLayer,
21 )
2022
2123
22class MoreBeer(SQLBase):24class MoreBeer(SQLBase):
@@ -28,6 +30,7 @@
2830
2931
30class TestInitZopeless(unittest.TestCase):32class TestInitZopeless(unittest.TestCase):
33
31 layer = LaunchpadScriptLayer34 layer = LaunchpadScriptLayer
3235
33 def test_initZopelessTwice(self):36 def test_initZopelessTwice(self):
@@ -47,10 +50,11 @@
47 # Calling initZopeless with the same arguments twice should return50 # Calling initZopeless with the same arguments twice should return
48 # the exact same object twice, but also emit a warning.51 # the exact same object twice, but also emit a warning.
49 try:52 try:
50 tm1 = initZopeless(dbname=PgTestSetup().dbname, dbhost='',53 dbname = DatabaseLayer._db_fixture.dbname
51 dbuser='launchpad')54 tm1 = initZopeless(
52 tm2 = initZopeless(dbname=PgTestSetup().dbname, dbhost='',55 dbname=dbname, dbhost='', dbuser='launchpad')
53 dbuser='launchpad')56 tm2 = initZopeless(
57 dbname=dbname, dbhost='', dbuser='launchpad')
54 self.failUnless(tm1 is tm2)58 self.failUnless(tm1 is tm2)
55 self.failUnless(self.warned)59 self.failUnless(self.warned)
56 finally:60 finally:
@@ -65,10 +69,11 @@
6569
6670
67class TestZopeless(unittest.TestCase):71class TestZopeless(unittest.TestCase):
72
68 layer = LaunchpadScriptLayer73 layer = LaunchpadScriptLayer
6974
70 def setUp(self):75 def setUp(self):
71 self.tm = initZopeless(dbname=PgTestSetup().dbname,76 self.tm = initZopeless(dbname=DatabaseLayer._db_fixture.dbname,
72 dbuser='launchpad')77 dbuser='launchpad')
7378
74 c = cursor()79 c = cursor()
@@ -182,7 +187,7 @@
182 self.tm.commit()187 self.tm.commit()
183188
184 # Make another change from a non-SQLObject connection, and commit that189 # Make another change from a non-SQLObject connection, and commit that
185 conn = psycopg2.connect('dbname=' + PgTestSetup().dbname)190 conn = psycopg2.connect('dbname=' + DatabaseLayer._db_fixture.dbname)
186 cur = conn.cursor()191 cur = conn.cursor()
187 cur.execute("BEGIN TRANSACTION;")192 cur.execute("BEGIN TRANSACTION;")
188 cur.execute("UPDATE MoreBeer SET rating=4 "193 cur.execute("UPDATE MoreBeer SET rating=4 "
@@ -202,7 +207,7 @@
202 >>> isZopeless()207 >>> isZopeless()
203 False208 False
204209
205 >>> tm = initZopeless(dbname=PgTestSetup().dbname,210 >>> tm = initZopeless(dbname=DatabaseLayer._db_fixture.dbname,
206 ... dbhost='', dbuser='launchpad')211 ... dbhost='', dbuser='launchpad')
207 >>> isZopeless()212 >>> isZopeless()
208 True213 True
209214
=== modified file 'lib/canonical/testing/ftests/test_layers.py'
--- lib/canonical/testing/ftests/test_layers.py 2010-07-26 13:18:18 +0000
+++ lib/canonical/testing/ftests/test_layers.py 2010-10-18 06:17:50 +0000
@@ -20,16 +20,27 @@
20from zope.component import getUtility, ComponentLookupError20from zope.component import getUtility, ComponentLookupError
2121
22from canonical.config import config, dbconfig22from canonical.config import config, dbconfig
23from canonical.launchpad.ftests.harness import LaunchpadTestSetup
24from lazr.config import as_host_port23from lazr.config import as_host_port
25from canonical.librarian.client import LibrarianClient, UploadFailed24from canonical.librarian.client import LibrarianClient, UploadFailed
26from canonical.librarian.interfaces import ILibrarianClient25from canonical.librarian.interfaces import ILibrarianClient
27from canonical.lazr.pidfile import pidfile_path26from canonical.lazr.pidfile import pidfile_path
28from canonical.testing.layers import (27from canonical.testing.layers import (
29 AppServerLayer, BaseLayer, DatabaseLayer, FunctionalLayer,28 AppServerLayer,
30 LaunchpadFunctionalLayer, LaunchpadLayer, LaunchpadScriptLayer,29 BaseLayer,
31 LaunchpadZopelessLayer, LayerInvariantError, LayerIsolationError,30 DatabaseLayer,
32 LayerProcessController, LibrarianLayer, MemcachedLayer, ZopelessLayer)31 FunctionalLayer,
32 LaunchpadFunctionalLayer,
33 LaunchpadLayer,
34 LaunchpadScriptLayer,
35 LaunchpadTestSetup,
36 LaunchpadZopelessLayer,
37 LayerInvariantError,
38 LayerIsolationError,
39 LayerProcessController,
40 LibrarianLayer,
41 MemcachedLayer,
42 ZopelessLayer,
43 )
33from lp.services.memcache.client import memcache_client_factory44from lp.services.memcache.client import memcache_client_factory
3445
35class BaseTestCase(unittest.TestCase):46class BaseTestCase(unittest.TestCase):
@@ -123,22 +134,13 @@
123 )134 )
124135
125 def testLaunchpadDbAvailable(self):136 def testLaunchpadDbAvailable(self):
126 try:137 if not self.want_launchpad_database:
127 con = DatabaseLayer.connect()138 self.assertEqual(None, DatabaseLayer._db_fixture)
128 cur = con.cursor()139 return
129 cur.execute("SELECT id FROM Person LIMIT 1")140 con = DatabaseLayer.connect()
130 if cur.fetchone() is not None:141 cur = con.cursor()
131 self.failUnless(142 cur.execute("SELECT id FROM Person LIMIT 1")
132 self.want_launchpad_database,143 self.assertNotEqual(None, cur.fetchone())
133 'Launchpad database should not be available.'
134 )
135 return
136 except psycopg2.Error:
137 pass
138 self.failIf(
139 self.want_launchpad_database,
140 'Launchpad database should be available but is not.'
141 )
142144
143 def testMemcachedWorking(self):145 def testMemcachedWorking(self):
144 client = MemcachedLayer.client or memcache_client_factory()146 client = MemcachedLayer.client or memcache_client_factory()
@@ -424,6 +426,9 @@
424 # The database should be reset by the test invariants.426 # The database should be reset by the test invariants.
425 LayerProcessController.startAppServer()427 LayerProcessController.startAppServer()
426 LayerProcessController.postTestInvariants()428 LayerProcessController.postTestInvariants()
429 # XXX: Robert Collins 2010-10-17 bug=661967 - this isn't a reset, its
430 # a flag that it *needs* a reset, which is actually quite different;
431 # the lack of a teardown will leak daabases.
427 self.assertEquals(True, LaunchpadTestSetup()._reset_db)432 self.assertEquals(True, LaunchpadTestSetup()._reset_db)
428433
429434
430435
=== modified file 'lib/canonical/testing/layers.py'
--- lib/canonical/testing/layers.py 2010-10-05 13:25:01 +0000
+++ lib/canonical/testing/layers.py 2010-10-18 06:17:50 +0000
@@ -35,6 +35,7 @@
35 'LaunchpadFunctionalLayer',35 'LaunchpadFunctionalLayer',
36 'LaunchpadLayer',36 'LaunchpadLayer',
37 'LaunchpadScriptLayer',37 'LaunchpadScriptLayer',
38 'LaunchpadTestSetup',
38 'LaunchpadZopelessLayer',39 'LaunchpadZopelessLayer',
39 'LayerInvariantError',40 'LayerInvariantError',
40 'LayerIsolationError',41 'LayerIsolationError',
@@ -92,6 +93,7 @@
92from zope.server.logger.pythonlogger import PythonLogger93from zope.server.logger.pythonlogger import PythonLogger
93from zope.testing.testrunner.runner import FakeInputContinueGenerator94from zope.testing.testrunner.runner import FakeInputContinueGenerator
9495
96from canonical.ftests.pgsql import PgTestSetup
95from canonical.launchpad.webapp.vhosts import allvhosts97from canonical.launchpad.webapp.vhosts import allvhosts
96from canonical.lazr import pidfile98from canonical.lazr import pidfile
97from canonical.config import CanonicalConfig, config, dbconfig99from canonical.config import CanonicalConfig, config, dbconfig
@@ -264,12 +266,14 @@
264 if not BaseLayer.persist_test_services:266 if not BaseLayer.persist_test_services:
265 kill_by_pidfile(MemcachedLayer.getPidFile(), num_polls=0)267 kill_by_pidfile(MemcachedLayer.getPidFile(), num_polls=0)
266 # Kill any database left lying around from a previous test run.268 # Kill any database left lying around from a previous test run.
269 db_fixture = LaunchpadTestSetup()
267 try:270 try:
268 DatabaseLayer.connect().close()271 db_fixture.connect().close()
269 except psycopg2.Error:272 except psycopg2.Error:
273 # We assume this means 'no test database exists.'
270 pass274 pass
271 else:275 else:
272 DatabaseLayer._dropDb()276 db_fixture.dropDb()
273277
274 @classmethod278 @classmethod
275 @profiled279 @profiled
@@ -693,19 +697,19 @@
693 _reset_between_tests = True697 _reset_between_tests = True
694698
695 _is_setup = False699 _is_setup = False
700 _db_fixture = None
696701
697 @classmethod702 @classmethod
698 @profiled703 @profiled
699 def setUp(cls):704 def setUp(cls):
700 cls._is_setup = True705 cls._is_setup = True
701 DatabaseLayer.force_dirty_database()706 # Read the sequences we'll need from the test template database.
702 # Imported here to avoid circular import issues. This707 reset_sequences_sql = LaunchpadTestSetup(
703 # functionality should be migrated into this module at some
704 # point. -- StuartBishop 20060712
705 from canonical.launchpad.ftests.harness import LaunchpadTestSetup
706 LaunchpadTestSetup().tearDown()
707 DatabaseLayer._reset_sequences_sql = LaunchpadTestSetup(
708 dbname='launchpad_ftest_template').generateResetSequencesSQL()708 dbname='launchpad_ftest_template').generateResetSequencesSQL()
709 cls._db_fixture = LaunchpadTestSetup(
710 reset_sequences_sql=reset_sequences_sql)
711 cls.force_dirty_database()
712 cls._db_fixture.tearDown()
709713
710 @classmethod714 @classmethod
711 @profiled715 @profiled
@@ -716,32 +720,22 @@
716 # Don't leave the DB lying around or it might break tests720 # Don't leave the DB lying around or it might break tests
717 # that depend on it not being there on startup, such as found721 # that depend on it not being there on startup, such as found
718 # in test_layers.py722 # in test_layers.py
719 DatabaseLayer.force_dirty_database()723 cls.force_dirty_database()
720 # Imported here to avoid circular import issues. This724 cls._db_fixture.tearDown()
721 # functionality should be migrated into this module at some725 cls._db_fixture = None
722 # point. -- StuartBishop 20060712
723 from canonical.launchpad.ftests.harness import LaunchpadTestSetup
724 LaunchpadTestSetup().tearDown()
725 DatabaseLayer._reset_sequences_sql = None
726726
727 @classmethod727 @classmethod
728 @profiled728 @profiled
729 def testSetUp(cls):729 def testSetUp(cls):
730 # Imported here to avoid circular import issues. This730 if cls._reset_between_tests:
731 # functionality should be migrated into this module at some731 cls._db_fixture.setUp()
732 # point. -- StuartBishop 20060712
733 from canonical.launchpad.ftests.harness import LaunchpadTestSetup
734 if DatabaseLayer._reset_between_tests:
735 LaunchpadTestSetup(
736 reset_sequences_sql=DatabaseLayer._reset_sequences_sql
737 ).setUp()
738 # Ensure that the database is connectable. Because we might have732 # Ensure that the database is connectable. Because we might have
739 # just created it, keep trying for a few seconds incase PostgreSQL733 # just created it, keep trying for a few seconds incase PostgreSQL
740 # is taking its time getting its house in order.734 # is taking its time getting its house in order.
741 attempts = 60735 attempts = 60
742 for count in range(0, attempts):736 for count in range(0, attempts):
743 try:737 try:
744 DatabaseLayer.connect().close()738 cls.connect().close()
745 except psycopg2.Error:739 except psycopg2.Error:
746 if count == attempts - 1:740 if count == attempts - 1:
747 raise741 raise
@@ -749,24 +743,20 @@
749 else:743 else:
750 break744 break
751745
752 if DatabaseLayer.use_mockdb is True:746 if cls.use_mockdb is True:
753 DatabaseLayer.installMockDb()747 cls.installMockDb()
754748
755 @classmethod749 @classmethod
756 @profiled750 @profiled
757 def testTearDown(cls):751 def testTearDown(cls):
758 if DatabaseLayer.use_mockdb is True:752 if cls.use_mockdb is True:
759 DatabaseLayer.uninstallMockDb()753 cls.uninstallMockDb()
760754
761 # Ensure that the database is connectable755 # Ensure that the database is connectable
762 DatabaseLayer.connect().close()756 cls.connect().close()
763757
764 # Imported here to avoid circular import issues. This758 if cls._reset_between_tests:
765 # functionality should be migrated into this module at some759 cls._db_fixture.tearDown()
766 # point. -- StuartBishop 20060712
767 from canonical.launchpad.ftests.harness import LaunchpadTestSetup
768 if DatabaseLayer._reset_between_tests:
769 LaunchpadTestSetup().tearDown()
770760
771 # Fail tests that forget to uninstall their database policies.761 # Fail tests that forget to uninstall their database policies.
772 from canonical.launchpad.webapp.adapter import StoreSelector762 from canonical.launchpad.webapp.adapter import StoreSelector
@@ -781,7 +771,7 @@
781 @classmethod771 @classmethod
782 @profiled772 @profiled
783 def installMockDb(cls):773 def installMockDb(cls):
784 assert DatabaseLayer.mockdb_mode is None, 'mock db already installed'774 assert cls.mockdb_mode is None, 'mock db already installed'
785775
786 from canonical.testing.mockdb import (776 from canonical.testing.mockdb import (
787 script_filename, ScriptRecorder, ScriptPlayer,777 script_filename, ScriptRecorder, ScriptPlayer,
@@ -795,32 +785,32 @@
795 # mock db script.785 # mock db script.
796 filename = script_filename(test_key)786 filename = script_filename(test_key)
797 if os.path.exists(filename):787 if os.path.exists(filename):
798 DatabaseLayer.mockdb_mode = 'replay'788 cls.mockdb_mode = 'replay'
799 DatabaseLayer.script = ScriptPlayer(test_key)789 cls.script = ScriptPlayer(test_key)
800 else:790 else:
801 DatabaseLayer.mockdb_mode = 'record'791 cls.mockdb_mode = 'record'
802 DatabaseLayer.script = ScriptRecorder(test_key)792 cls.script = ScriptRecorder(test_key)
803793
804 global _org_connect794 global _org_connect
805 _org_connect = psycopg2.connect795 _org_connect = psycopg2.connect
806 # Proxy real connections with our mockdb.796 # Proxy real connections with our mockdb.
807 def fake_connect(*args, **kw):797 def fake_connect(*args, **kw):
808 return DatabaseLayer.script.connect(_org_connect, *args, **kw)798 return cls.script.connect(_org_connect, *args, **kw)
809 psycopg2.connect = fake_connect799 psycopg2.connect = fake_connect
810800
811 @classmethod801 @classmethod
812 @profiled802 @profiled
813 def uninstallMockDb(cls):803 def uninstallMockDb(cls):
814 if DatabaseLayer.mockdb_mode is None:804 if cls.mockdb_mode is None:
815 return # Already uninstalled805 return # Already uninstalled
816806
817 # Store results if we are recording807 # Store results if we are recording
818 if DatabaseLayer.mockdb_mode == 'record':808 if cls.mockdb_mode == 'record':
819 DatabaseLayer.script.store()809 cls.script.store()
820 assert os.path.exists(DatabaseLayer.script.script_filename), (810 assert os.path.exists(cls.script.script_filename), (
821 "Stored results but no script on disk.")811 "Stored results but no script on disk.")
822812
823 DatabaseLayer.mockdb_mode = None813 cls.mockdb_mode = None
824 global _org_connect814 global _org_connect
825 psycopg2.connect = _org_connect815 psycopg2.connect = _org_connect
826 _org_connect = None816 _org_connect = None
@@ -828,20 +818,17 @@
828 @classmethod818 @classmethod
829 @profiled819 @profiled
830 def force_dirty_database(cls):820 def force_dirty_database(cls):
831 from canonical.launchpad.ftests.harness import LaunchpadTestSetup821 cls._db_fixture.force_dirty_database()
832 LaunchpadTestSetup().force_dirty_database()
833822
834 @classmethod823 @classmethod
835 @profiled824 @profiled
836 def connect(cls):825 def connect(cls):
837 from canonical.launchpad.ftests.harness import LaunchpadTestSetup826 return cls._db_fixture.connect()
838 return LaunchpadTestSetup().connect()
839827
840 @classmethod828 @classmethod
841 @profiled829 @profiled
842 def _dropDb(cls):830 def _dropDb(cls):
843 from canonical.launchpad.ftests.harness import LaunchpadTestSetup831 return cls._db_fixture.dropDb()
844 return LaunchpadTestSetup().dropDb()
845832
846833
847def test_default_timeout():834def test_default_timeout():
@@ -1378,6 +1365,11 @@
1378 reconnect_stores(database_config_section=database_config_section)1365 reconnect_stores(database_config_section=database_config_section)
13791366
13801367
1368class LaunchpadTestSetup(PgTestSetup):
1369 template = 'launchpad_ftest_template'
1370 dbuser = 'launchpad'
1371
1372
1381class LaunchpadZopelessLayer(LaunchpadScriptLayer):1373class LaunchpadZopelessLayer(LaunchpadScriptLayer):
1382 """Full Zopeless environment including Component Architecture and1374 """Full Zopeless environment including Component Architecture and
1383 database connections initialized.1375 database connections initialized.
@@ -1643,6 +1635,9 @@
1643 # configs/testrunner-appserver/mail-configure.zcml1635 # configs/testrunner-appserver/mail-configure.zcml
1644 smtp_controller = None1636 smtp_controller = None
16451637
1638 # The DB fixture in use
1639 _db_fixture = None
1640
1646 @classmethod1641 @classmethod
1647 @profiled1642 @profiled
1648 def startSMTPServer(cls):1643 def startSMTPServer(cls):
@@ -1770,9 +1765,12 @@
1770 @classmethod1765 @classmethod
1771 def _runAppServer(cls):1766 def _runAppServer(cls):
1772 """Start the app server using runlaunchpad.py"""1767 """Start the app server using runlaunchpad.py"""
1773 from canonical.launchpad.ftests.harness import LaunchpadTestSetup
1774 # The database must be available for the app server to start.1768 # The database must be available for the app server to start.
1775 LaunchpadTestSetup().setUp()1769 cls._db_fixture = LaunchpadTestSetup()
1770 # This is not torn down properly: rather the singleton nature is abused
1771 # and the fixture is simply marked as being dirty.
1772 # XXX: Robert Collins 2010-10-17 bug=661967
1773 cls._db_fixture.setUp()
1776 # The app server will not start at all if the database hasn't been1774 # The app server will not start at all if the database hasn't been
1777 # correctly patched. The app server will make exactly this check,1775 # correctly patched. The app server will make exactly this check,
1778 # doing it here makes the error more obvious.1776 # doing it here makes the error more obvious.
17791777
=== modified file 'lib/lp/bugs/doc/bug-heat.txt'
--- lib/lp/bugs/doc/bug-heat.txt 2010-08-23 22:05:26 +0000
+++ lib/lp/bugs/doc/bug-heat.txt 2010-10-18 06:17:50 +0000
@@ -250,7 +250,7 @@
250The BugHeatUpdater class is used to create bug heat calculation jobs for250The BugHeatUpdater class is used to create bug heat calculation jobs for
251bugs with out-of-date heat.251bugs with out-of-date heat.
252252
253 >>> from canonical.launchpad.scripts.garbo import BugHeatUpdater253 >>> from lp.scripts.garbo import BugHeatUpdater
254 >>> from canonical.launchpad.scripts import FakeLogger254 >>> from canonical.launchpad.scripts import FakeLogger
255255
256We'll commit the transaction so that the BugHeatUpdater updates the256We'll commit the transaction so that the BugHeatUpdater updates the
257257
=== modified file 'lib/lp/bugs/tests/test_bugwatch.py'
--- lib/lp/bugs/tests/test_bugwatch.py 2010-10-04 19:50:45 +0000
+++ lib/lp/bugs/tests/test_bugwatch.py 2010-10-18 06:17:50 +0000
@@ -26,7 +26,7 @@
26 login,26 login,
27 )27 )
28from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities28from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
29from canonical.launchpad.scripts.garbo import BugWatchActivityPruner29from lp.scripts.garbo import BugWatchActivityPruner
30from canonical.launchpad.scripts.logger import QuietFakeLogger30from canonical.launchpad.scripts.logger import QuietFakeLogger
31from canonical.launchpad.webapp import urlsplit31from canonical.launchpad.webapp import urlsplit
32from canonical.testing.layers import (32from canonical.testing.layers import (
3333
=== modified file 'lib/lp/code/model/tests/test_revision.py'
--- lib/lp/code/model/tests/test_revision.py 2010-10-04 19:50:45 +0000
+++ lib/lp/code/model/tests/test_revision.py 2010-10-18 06:17:50 +0000
@@ -28,7 +28,7 @@
28 )28 )
29from canonical.launchpad.interfaces.account import AccountStatus29from canonical.launchpad.interfaces.account import AccountStatus
30from canonical.launchpad.interfaces.lpstorm import IMasterObject30from canonical.launchpad.interfaces.lpstorm import IMasterObject
31from canonical.launchpad.scripts.garbo import RevisionAuthorEmailLinker31from lp.scripts.garbo import RevisionAuthorEmailLinker
32from canonical.launchpad.webapp.interfaces import (32from canonical.launchpad.webapp.interfaces import (
33 DEFAULT_FLAVOR,33 DEFAULT_FLAVOR,
34 IStoreSelector,34 IStoreSelector,
3535
=== modified file 'lib/lp/code/model/tests/test_revisionauthor.py'
--- lib/lp/code/model/tests/test_revisionauthor.py 2010-10-04 19:50:45 +0000
+++ lib/lp/code/model/tests/test_revisionauthor.py 2010-10-18 06:17:50 +0000
@@ -12,7 +12,7 @@
1212
13from canonical.config import config13from canonical.config import config
14from canonical.launchpad.interfaces.emailaddress import EmailAddressStatus14from canonical.launchpad.interfaces.emailaddress import EmailAddressStatus
15from canonical.launchpad.scripts.garbo import RevisionAuthorEmailLinker15from lp.scripts.garbo import RevisionAuthorEmailLinker
16from canonical.testing.layers import LaunchpadZopelessLayer16from canonical.testing.layers import LaunchpadZopelessLayer
17from lp.code.model.revision import (17from lp.code.model.revision import (
18 RevisionAuthor,18 RevisionAuthor,
1919
=== modified file 'lib/lp/code/scripts/tests/test_revisionkarma.py'
--- lib/lp/code/scripts/tests/test_revisionkarma.py 2010-10-04 19:50:45 +0000
+++ lib/lp/code/scripts/tests/test_revisionkarma.py 2010-10-18 06:17:50 +0000
@@ -12,7 +12,7 @@
1212
13from canonical.config import config13from canonical.config import config
14from canonical.launchpad.database.emailaddress import EmailAddressSet14from canonical.launchpad.database.emailaddress import EmailAddressSet
15from canonical.launchpad.scripts.garbo import RevisionAuthorEmailLinker15from lp.scripts.garbo import RevisionAuthorEmailLinker
16from canonical.testing.layers import LaunchpadZopelessLayer16from canonical.testing.layers import LaunchpadZopelessLayer
17from lp.code.model.revision import RevisionSet17from lp.code.model.revision import RevisionSet
18from lp.code.scripts.revisionkarma import RevisionKarmaAllocator18from lp.code.scripts.revisionkarma import RevisionKarmaAllocator
1919
=== modified file 'lib/lp/codehosting/tests/test_acceptance.py'
--- lib/lp/codehosting/tests/test_acceptance.py 2010-10-04 19:50:45 +0000
+++ lib/lp/codehosting/tests/test_acceptance.py 2010-10-18 06:17:50 +0000
@@ -21,7 +21,6 @@
21from zope.component import getUtility21from zope.component import getUtility
2222
23from canonical.config import config23from canonical.config import config
24from canonical.launchpad.ftests.harness import LaunchpadZopelessTestSetup
25from canonical.testing.layers import ZopelessAppServerLayer24from canonical.testing.layers import ZopelessAppServerLayer
26from canonical.testing.profiled import profiled25from canonical.testing.profiled import profiled
27from lp.code.bzr import (26from lp.code.bzr import (
@@ -334,7 +333,7 @@
334 remote_url = self.getTransportURL('~testuser/+junk/test-branch')333 remote_url = self.getTransportURL('~testuser/+junk/test-branch')
335 self.push(self.local_branch_path, remote_url)334 self.push(self.local_branch_path, remote_url)
336 self.assertBranchesMatch(self.local_branch_path, remote_url)335 self.assertBranchesMatch(self.local_branch_path, remote_url)
337 LaunchpadZopelessTestSetup().txn.begin()336 ZopelessAppServerLayer.txn.begin()
338 db_branch = getUtility(IBranchSet).getByUniqueName(337 db_branch = getUtility(IBranchSet).getByUniqueName(
339 '~testuser/+junk/test-branch')338 '~testuser/+junk/test-branch')
340 self.assertEqual(339 self.assertEqual(
@@ -343,7 +342,7 @@
343 BranchFormat.BZR_BRANCH_7, db_branch.branch_format)342 BranchFormat.BZR_BRANCH_7, db_branch.branch_format)
344 self.assertEqual(343 self.assertEqual(
345 ControlFormat.BZR_METADIR_1, db_branch.control_format)344 ControlFormat.BZR_METADIR_1, db_branch.control_format)
346 LaunchpadZopelessTestSetup().txn.commit()345 ZopelessAppServerLayer.txn.commit()
347346
348 def test_push_to_existing_branch(self):347 def test_push_to_existing_branch(self):
349 """Pushing to an existing branch must work."""348 """Pushing to an existing branch must work."""
@@ -374,12 +373,12 @@
374 self.push(self.local_branch_path, remote_url)373 self.push(self.local_branch_path, remote_url)
375374
376 # Rename owner, product and branch in the database375 # Rename owner, product and branch in the database
377 LaunchpadZopelessTestSetup().txn.begin()376 ZopelessAppServerLayer.txn.begin()
378 branch = self.getDatabaseBranch('testuser', None, 'test-branch')377 branch = self.getDatabaseBranch('testuser', None, 'test-branch')
379 branch.owner.name = 'renamed-user'378 branch.owner.name = 'renamed-user'
380 branch.setTarget(user=branch.owner, project=Product.byName('firefox'))379 branch.setTarget(user=branch.owner, project=Product.byName('firefox'))
381 branch.name = 'renamed-branch'380 branch.name = 'renamed-branch'
382 LaunchpadZopelessTestSetup().txn.commit()381 ZopelessAppServerLayer.txn.commit()
383382
384 # Check that it's not at the old location.383 # Check that it's not at the old location.
385 self.assertNotBranch(384 self.assertNotBranch(
@@ -405,23 +404,23 @@
405 '~testuser/+junk/totally-new-branch')404 '~testuser/+junk/totally-new-branch')
406 self.push(self.local_branch_path, remote_url)405 self.push(self.local_branch_path, remote_url)
407406
408 LaunchpadZopelessTestSetup().txn.begin()407 ZopelessAppServerLayer.txn.begin()
409 branch = self.getDatabaseBranch(408 branch = self.getDatabaseBranch(
410 'testuser', None, 'totally-new-branch')409 'testuser', None, 'totally-new-branch')
411410
412 self.assertEqual(411 self.assertEqual(
413 ['~testuser/+junk/totally-new-branch', self.revid],412 ['~testuser/+junk/totally-new-branch', self.revid],
414 [branch.unique_name, branch.last_mirrored_id])413 [branch.unique_name, branch.last_mirrored_id])
415 LaunchpadZopelessTestSetup().txn.abort()414 ZopelessAppServerLayer.txn.abort()
416415
417 def test_record_default_stacking(self):416 def test_record_default_stacking(self):
418 # If the location being pushed to has a default stacked-on branch,417 # If the location being pushed to has a default stacked-on branch,
419 # then branches pushed to that location end up stacked on it by418 # then branches pushed to that location end up stacked on it by
420 # default.419 # default.
421 product = self.factory.makeProduct()420 product = self.factory.makeProduct()
422 LaunchpadZopelessTestSetup().txn.commit()421 ZopelessAppServerLayer.txn.commit()
423422
424 LaunchpadZopelessTestSetup().txn.begin()423 ZopelessAppServerLayer.txn.begin()
425424
426 self.make_branch_and_tree('stacked-on')425 self.make_branch_and_tree('stacked-on')
427 trunk_unique_name = '~testuser/%s/trunk' % product.name426 trunk_unique_name = '~testuser/%s/trunk' % product.name
@@ -431,7 +430,7 @@
431 self.factory.enableDefaultStackingForProduct(430 self.factory.enableDefaultStackingForProduct(
432 db_trunk.product, db_trunk)431 db_trunk.product, db_trunk)
433432
434 LaunchpadZopelessTestSetup().txn.commit()433 ZopelessAppServerLayer.txn.commit()
435434
436 stacked_unique_name = '~testuser/%s/stacked' % product.name435 stacked_unique_name = '~testuser/%s/stacked' % product.name
437 self.push(436 self.push(
@@ -447,7 +446,7 @@
447 # attribute of the database branch, and stacked on location of the new446 # attribute of the database branch, and stacked on location of the new
448 # branch is normalized to be a relative path.447 # branch is normalized to be a relative path.
449 product = self.factory.makeProduct()448 product = self.factory.makeProduct()
450 LaunchpadZopelessTestSetup().txn.commit()449 ZopelessAppServerLayer.txn.commit()
451450
452 self.make_branch_and_tree('stacked-on')451 self.make_branch_and_tree('stacked-on')
453 trunk_unique_name = '~testuser/%s/trunk' % product.name452 trunk_unique_name = '~testuser/%s/trunk' % product.name
@@ -507,11 +506,11 @@
507 def test_push_to_new_short_branch_alias(self):506 def test_push_to_new_short_branch_alias(self):
508 # We can also push branches to URLs like /+branch/firefox507 # We can also push branches to URLs like /+branch/firefox
509 # Hack 'firefox' so we have permission to do this.508 # Hack 'firefox' so we have permission to do this.
510 LaunchpadZopelessTestSetup().txn.begin()509 ZopelessAppServerLayer.txn.begin()
511 firefox = Product.selectOneBy(name='firefox')510 firefox = Product.selectOneBy(name='firefox')
512 testuser = Person.selectOneBy(name='testuser')511 testuser = Person.selectOneBy(name='testuser')
513 firefox.development_focus.owner = testuser512 firefox.development_focus.owner = testuser
514 LaunchpadZopelessTestSetup().txn.commit()513 ZopelessAppServerLayer.txn.commit()
515 remote_url = self.getTransportURL('+branch/firefox')514 remote_url = self.getTransportURL('+branch/firefox')
516 self.push(self.local_branch_path, remote_url)515 self.push(self.local_branch_path, remote_url)
517 self.assertBranchesMatch(self.local_branch_path, remote_url)516 self.assertBranchesMatch(self.local_branch_path, remote_url)
@@ -520,10 +519,10 @@
520 # If a hosted branch exists in the database, but not on the519 # If a hosted branch exists in the database, but not on the
521 # filesystem, and is writable by the user, then the user is able to520 # filesystem, and is writable by the user, then the user is able to
522 # push to it.521 # push to it.
523 LaunchpadZopelessTestSetup().txn.begin()522 ZopelessAppServerLayer.txn.begin()
524 branch = self.makeDatabaseBranch('testuser', 'firefox', 'some-branch')523 branch = self.makeDatabaseBranch('testuser', 'firefox', 'some-branch')
525 remote_url = self.getTransportURL(branch.unique_name)524 remote_url = self.getTransportURL(branch.unique_name)
526 LaunchpadZopelessTestSetup().txn.commit()525 ZopelessAppServerLayer.txn.commit()
527 self.push(526 self.push(
528 self.local_branch_path, remote_url,527 self.local_branch_path, remote_url,
529 extra_args=['--use-existing-dir'])528 extra_args=['--use-existing-dir'])
@@ -531,21 +530,21 @@
531530
532 def test_cant_push_to_existing_mirrored_branch(self):531 def test_cant_push_to_existing_mirrored_branch(self):
533 # Users cannot push to mirrored branches.532 # Users cannot push to mirrored branches.
534 LaunchpadZopelessTestSetup().txn.begin()533 ZopelessAppServerLayer.txn.begin()
535 branch = self.makeDatabaseBranch(534 branch = self.makeDatabaseBranch(
536 'testuser', 'firefox', 'some-branch', BranchType.MIRRORED)535 'testuser', 'firefox', 'some-branch', BranchType.MIRRORED)
537 remote_url = self.getTransportURL(branch.unique_name)536 remote_url = self.getTransportURL(branch.unique_name)
538 LaunchpadZopelessTestSetup().txn.commit()537 ZopelessAppServerLayer.txn.commit()
539 self.assertCantPush(538 self.assertCantPush(
540 self.local_branch_path, remote_url,539 self.local_branch_path, remote_url,
541 ['Permission denied:', 'Transport operation not possible:'])540 ['Permission denied:', 'Transport operation not possible:'])
542541
543 def test_cant_push_to_existing_unowned_hosted_branch(self):542 def test_cant_push_to_existing_unowned_hosted_branch(self):
544 # Users can only push to hosted branches that they own.543 # Users can only push to hosted branches that they own.
545 LaunchpadZopelessTestSetup().txn.begin()544 ZopelessAppServerLayer.txn.begin()
546 branch = self.makeDatabaseBranch('mark', 'firefox', 'some-branch')545 branch = self.makeDatabaseBranch('mark', 'firefox', 'some-branch')
547 remote_url = self.getTransportURL(branch.unique_name)546 remote_url = self.getTransportURL(branch.unique_name)
548 LaunchpadZopelessTestSetup().txn.commit()547 ZopelessAppServerLayer.txn.commit()
549 self.assertCantPush(548 self.assertCantPush(
550 self.local_branch_path, remote_url,549 self.local_branch_path, remote_url,
551 ['Permission denied:', 'Transport operation not possible:'])550 ['Permission denied:', 'Transport operation not possible:'])
@@ -566,12 +565,12 @@
566 person_name, product_name, branch_name)565 person_name, product_name, branch_name)
567566
568 # Mark as mirrored.567 # Mark as mirrored.
569 LaunchpadZopelessTestSetup().txn.begin()568 ZopelessAppServerLayer.txn.begin()
570 branch = self.getDatabaseBranch(569 branch = self.getDatabaseBranch(
571 person_name, product_name, branch_name)570 person_name, product_name, branch_name)
572 branch.branch_type = BranchType.MIRRORED571 branch.branch_type = BranchType.MIRRORED
573 branch.url = "http://example.com/smartservertest/branch"572 branch.url = "http://example.com/smartservertest/branch"
574 LaunchpadZopelessTestSetup().txn.commit()573 ZopelessAppServerLayer.txn.commit()
575 return ro_branch_url574 return ro_branch_url
576575
577 def test_can_read_readonly_branch(self):576 def test_can_read_readonly_branch(self):
578577
=== modified file 'lib/lp/hardwaredb/doc/hwdb.txt'
--- lib/lp/hardwaredb/doc/hwdb.txt 2010-10-09 16:36:22 +0000
+++ lib/lp/hardwaredb/doc/hwdb.txt 2010-10-18 06:17:50 +0000
@@ -375,7 +375,7 @@
375 ... u'beeblebrox@example.com')375 ... u'beeblebrox@example.com')
376 >>> user.validateAndEnsurePreferredEmail(email)376 >>> user.validateAndEnsurePreferredEmail(email)
377 >>> transaction.commit()377 >>> transaction.commit()
378 >>> from canonical.launchpad.scripts.garbo import HWSubmissionEmailLinker378 >>> from lp.scripts.garbo import HWSubmissionEmailLinker
379 >>> from lp.testing.logger import MockLogger379 >>> from lp.testing.logger import MockLogger
380 >>> HWSubmissionEmailLinker(log=MockLogger()).run()380 >>> HWSubmissionEmailLinker(log=MockLogger()).run()
381 >>> submission = hw_submission_set.getBySubmissionKey(u'unique-id-2')381 >>> submission = hw_submission_set.getBySubmissionKey(u'unique-id-2')
382382
=== modified file 'lib/lp/poppy/tests/test_poppy.py'
--- lib/lp/poppy/tests/test_poppy.py 2010-09-28 22:33:42 +0000
+++ lib/lp/poppy/tests/test_poppy.py 2010-10-18 06:17:50 +0000
@@ -49,7 +49,7 @@
49 self.root_dir, port=self.port, cmd='echo CLOSED')49 self.root_dir, port=self.port, cmd='echo CLOSED')
50 self.poppy.startPoppy()50 self.poppy.startPoppy()
5151
52 def tearDown(self):52 def cleanUp(self):
53 self.poppy.killPoppy()53 self.poppy.killPoppy()
5454
55 def getTransport(self):55 def getTransport(self):
@@ -129,7 +129,7 @@
129 self._tac = PoppyTac(self.root_dir)129 self._tac = PoppyTac(self.root_dir)
130 self._tac.setUp()130 self._tac.setUp()
131131
132 def tearDown(self):132 def cleanUp(self):
133 shutil.rmtree(self._home_dir)133 shutil.rmtree(self._home_dir)
134 os.environ['HOME'] = self._current_home134 os.environ['HOME'] = self._current_home
135 self._tac.tearDown()135 self._tac.tearDown()
@@ -199,7 +199,7 @@
199 super(TestPoppy, self).setUp()199 super(TestPoppy, self).setUp()
200 self.root_dir = self.makeTemporaryDirectory()200 self.root_dir = self.makeTemporaryDirectory()
201 self.server = self.server_factory(self.root_dir, self.factory)201 self.server = self.server_factory(self.root_dir, self.factory)
202 self.installFixture(self.server)202 self.useFixture(self.server)
203203
204 def _uploadPath(self, path):204 def _uploadPath(self, path):
205 """Return system path of specified path inside an upload.205 """Return system path of specified path inside an upload.
206206
=== modified file 'lib/lp/registry/interfaces/distroseries.py'
--- lib/lp/registry/interfaces/distroseries.py 2010-10-15 10:54:34 +0000
+++ lib/lp/registry/interfaces/distroseries.py 2010-10-18 06:17:50 +0000
@@ -8,6 +8,7 @@
8__metaclass__ = type8__metaclass__ = type
99
10__all__ = [10__all__ = [
11 'DerivationError',
11 'IDistroSeries',12 'IDistroSeries',
12 'IDistroSeriesEditRestricted',13 'IDistroSeriesEditRestricted',
13 'IDistroSeriesPublic',14 'IDistroSeriesPublic',
@@ -16,20 +17,25 @@
1617
17from lazr.enum import DBEnumeratedType18from lazr.enum import DBEnumeratedType
18from lazr.restful.declarations import (19from lazr.restful.declarations import (
20 call_with,
19 export_as_webservice_entry,21 export_as_webservice_entry,
20 export_factory_operation,22 export_factory_operation,
21 export_read_operation,23 export_read_operation,
24 export_write_operation,
22 exported,25 exported,
23 LAZR_WEBSERVICE_EXPORTED,26 LAZR_WEBSERVICE_EXPORTED,
24 operation_parameters,27 operation_parameters,
25 operation_returns_collection_of,28 operation_returns_collection_of,
26 operation_returns_entry,29 operation_returns_entry,
27 rename_parameters_as,30 rename_parameters_as,
31 REQUEST_USER,
32 webservice_error,
28 )33 )
29from lazr.restful.fields import (34from lazr.restful.fields import (
30 Reference,35 Reference,
31 ReferenceChoice,36 ReferenceChoice,
32 )37 )
38from lazr.restful.interface import copy_field
33from zope.component import getUtility39from zope.component import getUtility
34from zope.interface import (40from zope.interface import (
35 Attribute,41 Attribute,
@@ -39,6 +45,7 @@
39 Bool,45 Bool,
40 Choice,46 Choice,
41 Datetime,47 Datetime,
48 List,
42 Object,49 Object,
43 TextLine,50 TextLine,
44 )51 )
@@ -673,8 +680,8 @@
673 If sourcename is passed, only packages that are built from680 If sourcename is passed, only packages that are built from
674 source packages by that name will be returned.681 source packages by that name will be returned.
675 If archive is passed, restricted the results to the given archive,682 If archive is passed, restricted the results to the given archive,
676 if it is suppressed the results will be restricted to the distribtion683 if it is suppressed the results will be restricted to the
677 'main_archive'.684 distribution 'main_archive'.
678 """685 """
679686
680 def getSourcePackagePublishing(status, pocket, component=None,687 def getSourcePackagePublishing(status, pocket, component=None,
@@ -683,8 +690,8 @@
683690
684 According status and pocket.691 According status and pocket.
685 If archive is passed, restricted the results to the given archive,692 If archive is passed, restricted the results to the given archive,
686 if it is suppressed the results will be restricted to the distribtion693 if it is suppressed the results will be restricted to the
687 'main_archive'.694 distribution 'main_archive'.
688 """695 """
689696
690 def getBinaryPackageCaches(archive=None):697 def getBinaryPackageCaches(archive=None):
@@ -789,6 +796,69 @@
789 :param format: The SourcePackageFormat to check.796 :param format: The SourcePackageFormat to check.
790 """797 """
791798
799 @operation_parameters(
800 name=copy_field(name, required=True),
801 displayname=copy_field(displayname, required=False),
802 title=copy_field(title, required=False),
803 summary=TextLine(
804 title=_("The summary of the distroseries to derive."),
805 required=False),
806 description=copy_field(description, required=False),
807 version=copy_field(version, required=False),
808 distribution=copy_field(distribution, required=False),
809 status=copy_field(status, required=False),
810 architectures=List(
811 title=_("The list of architectures to copy to the derived "
812 "distroseries."),
813 required=False),
814 packagesets=List(
815 title=_("The list of packagesets to copy to the derived "
816 "distroseries"),
817 required=False),
818 rebuild=Bool(
819 title=_("If binaries will be copied to the derived "
820 "distroseries."),
821 required=True),
822 )
823 @call_with(user=REQUEST_USER)
824 @export_write_operation()
825 def deriveDistroSeries(user, name, displayname, title, summary,
826 description, version, distribution, status,
827 architectures, packagesets, rebuild):
828 """Derive a distroseries from this one.
829
830 This method performs checks, can create the new distroseries if
831 necessary, and then creates a job to populate the new
832 distroseries.
833
834 :param name: The name of the new distroseries we will create if it
835 doesn't exist, or the name of the distroseries we will look
836 up, and then initialise.
837 :param displayname: The Display Name for the new distroseries.
838 If the distroseries already exists this parameter is ignored.
839 :param title: The Title for the new distroseries. If the
840 distroseries already exists this parameter is ignored.
841 :param summary: The Summary for the new distroseries. If the
842 distroseries already exists this parameter is ignored.
843 :param description: The Description for the new distroseries. If the
844 distroseries already exists this parameter is ignored.
845 :param version: The version for the new distroseries. If the
846 distroseries already exists this parameter is ignored.
847 :param distribution: The distribution the derived series will
848 belong to. If it isn't specified this distroseries'
849 distribution is used.
850 :param status: The status the new distroseries will be created
851 in. If the distroseries isn't specified, this parameter will
852 be ignored. Defaults to FROZEN.
853 :param architectures: The architectures to copy to the derived
854 series. If not specified, all of the architectures are copied.
855 :param packagesets: The packagesets to copy to the derived series.
856 If not specified, all of the packagesets are copied.
857 :param rebuild: Whether binaries will be copied to the derived
858 series. If it's true, they will not be, and if it's false, they
859 will be.
860 """
861
792862
793class IDistroSeries(IDistroSeriesEditRestricted, IDistroSeriesPublic,863class IDistroSeries(IDistroSeriesEditRestricted, IDistroSeriesPublic,
794 IStructuralSubscriptionTarget):864 IStructuralSubscriptionTarget):
@@ -856,5 +926,11 @@
856 """926 """
857927
858928
929class DerivationError(Exception):
930 """Raised when there is a problem deriving a distroseries."""
931 webservice_error(400) # Bad Request
932 _message_prefix = "Error deriving distro series"
933
934
859# Monkey patch for circular import avoidance done in935# Monkey patch for circular import avoidance done in
860# _schema_circular_imports.py936# _schema_circular_imports.py
861937
=== modified file 'lib/lp/registry/model/distroseries.py'
--- lib/lp/registry/model/distroseries.py 2010-10-15 10:54:34 +0000
+++ lib/lp/registry/model/distroseries.py 2010-10-18 06:17:50 +0000
@@ -35,6 +35,7 @@
35 )35 )
36from zope.component import getUtility36from zope.component import getUtility
37from zope.interface import implements37from zope.interface import implements
38from zope.security.interfaces import Unauthorized
3839
39from canonical.database.constants import (40from canonical.database.constants import (
40 DEFAULT,41 DEFAULT,
@@ -88,6 +89,7 @@
88 )89 )
89from lp.bugs.model.bugtask import BugTask90from lp.bugs.model.bugtask import BugTask
90from lp.registry.interfaces.distroseries import (91from lp.registry.interfaces.distroseries import (
92 DerivationError,
91 IDistroSeries,93 IDistroSeries,
92 IDistroSeriesSet,94 IDistroSeriesSet,
93 )95 )
@@ -128,6 +130,9 @@
128from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuildSet130from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuildSet
129from lp.soyuz.interfaces.binarypackagename import IBinaryPackageName131from lp.soyuz.interfaces.binarypackagename import IBinaryPackageName
130from lp.soyuz.interfaces.buildrecords import IHasBuildRecords132from lp.soyuz.interfaces.buildrecords import IHasBuildRecords
133from lp.soyuz.interfaces.distributionjob import (
134 IInitialiseDistroSeriesJobSource,
135 )
131from lp.soyuz.interfaces.publishing import (136from lp.soyuz.interfaces.publishing import (
132 active_publishing_status,137 active_publishing_status,
133 ICanPublishPackages,138 ICanPublishPackages,
@@ -162,6 +167,10 @@
162 )167 )
163from lp.soyuz.model.section import Section168from lp.soyuz.model.section import Section
164from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease169from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
170from lp.soyuz.scripts.initialise_distroseries import (
171 InitialisationError,
172 InitialiseDistroSeries,
173 )
165from lp.translations.interfaces.languagepack import LanguagePackType174from lp.translations.interfaces.languagepack import LanguagePackType
166from lp.translations.model.distroseries_translations_copy import (175from lp.translations.model.distroseries_translations_copy import (
167 copy_active_translations,176 copy_active_translations,
@@ -1847,6 +1856,59 @@
1847 ISourcePackageFormatSelectionSet).getBySeriesAndFormat(1856 ISourcePackageFormatSelectionSet).getBySeriesAndFormat(
1848 self, format) is not None1857 self, format) is not None
18491858
1859 def deriveDistroSeries(self, user, name, distribution=None,
1860 displayname=None, title=None, summary=None,
1861 description=None, version=None,
1862 status=SeriesStatus.FROZEN, architectures=(),
1863 packagesets=(), rebuild=False):
1864 """See `IDistroSeries`."""
1865 # XXX StevenK bug=643369 This should be in the security adapter
1866 # This should be allowed if the user is a driver for self.parent
1867 # or the child.parent's drivers.
1868 if not (user.inTeam('soyuz-team') or user.inTeam('admins')):
1869 raise Unauthorized
1870 child = IStore(self).find(DistroSeries, name=name).one()
1871 if child is None:
1872 if distribution is None:
1873 distribution = self.distribution
1874 if not displayname:
1875 raise DerivationError(
1876 "Display Name needs to be set when creating a "
1877 "distroseries.")
1878 if not title:
1879 raise DerivationError(
1880 "Title needs to be set when creating a distroseries.")
1881 if not summary:
1882 raise DerivationError(
1883 "Summary needs to be set when creating a "
1884 "distroseries.")
1885 if not description:
1886 raise DerivationError(
1887 "Description needs to be set when creating a "
1888 "distroseries.")
1889 if not version:
1890 raise DerivationError(
1891 "Version needs to be set when creating a "
1892 "distroseries.")
1893 child = distribution.newSeries(
1894 name=name, displayname=displayname, title=title,
1895 summary=summary, description=description,
1896 version=version, parent_series=self, owner=user)
1897 child.status = status
1898 IStore(self).add(child)
1899 else:
1900 if child.parent_series is not self:
1901 raise DerivationError(
1902 "DistroSeries %s parent series isn't %s" % (
1903 child.name, self.name))
1904 initialise_series = InitialiseDistroSeries(child)
1905 try:
1906 initialise_series.check()
1907 except InitialisationError, e:
1908 raise DerivationError(e)
1909 getUtility(IInitialiseDistroSeriesJobSource).create(
1910 child, architectures, packagesets, rebuild)
1911
18501912
1851class DistroSeriesSet:1913class DistroSeriesSet:
1852 implements(IDistroSeriesSet)1914 implements(IDistroSeriesSet)
18531915
=== added file 'lib/lp/registry/stories/webservice/xx-derivedistroseries.txt'
--- lib/lp/registry/stories/webservice/xx-derivedistroseries.txt 1970-01-01 00:00:00 +0000
+++ lib/lp/registry/stories/webservice/xx-derivedistroseries.txt 2010-10-18 06:17:50 +0000
@@ -0,0 +1,68 @@
1Derive Distributions
2--------------------
3
4Using the DistroSeries.deriveDistroSeries() function, we can call it with the
5parent distroseries. We can call it with the distroseries already created,
6or it can create it for the user.
7
8Set Up
9------
10
11 >>> login('admin@canonical.com')
12 >>> soyuz = factory.makeTeam(name='soyuz-team')
13 >>> parent = factory.makeDistroSeries()
14 >>> child = factory.makeDistroSeries(parent_series=parent)
15 >>> other = factory.makeDistroSeries()
16 >>> logout()
17 >>> from canonical.launchpad.testing.pages import webservice_for_person
18 >>> from canonical.launchpad.webapp.interfaces import OAuthPermission
19 >>> soyuz_webservice = webservice_for_person(
20 ... soyuz.teamowner, permission=OAuthPermission.WRITE_PUBLIC)
21
22Calling
23-------
24
25We can't call .deriveDistroSeries() with a distroseries that isn't the
26child's parent
27
28 >>> series_url = '/%s/%s' % (parent.parent.name, parent.name)
29 >>> other_series_url = '/%s/%s' % (
30 ... other.parent.name, other.name)
31 >>> child_name = child.name
32 >>> series = webservice.get(series_url).jsonBody()
33 >>> other_series = webservice.get(other_series_url).jsonBody()
34 >>> derived = soyuz_webservice.named_post(
35 ... other_series['self_link'], 'deriveDistroSeries', {},
36 ... name=child_name, rebuild=False)
37 >>> print derived
38 HTTP/1.1 400 Bad Request
39 Status: 400 Bad Request
40 ...
41 <BLANKLINE>
42 DistroSeries ... parent series isn't ...
43 <BLANKLINE>
44 ...
45
46If we call it correctly, it works.
47
48 >>> derived = soyuz_webservice.named_post(
49 ... series['self_link'], 'deriveDistroSeries', {},
50 ... name=child_name, rebuild=False)
51 >>> print derived
52 HTTP/1.1 200 Ok
53 Status: 200 Ok
54 ...
55 <BLANKLINE>
56 ...
57
58And we can verify the job exists.
59
60 >>> from zope.component import getUtility
61 >>> from lp.soyuz.interfaces.distributionjob import (
62 ... IInitialiseDistroSeriesJobSource)
63 >>> login('admin@canonical.com')
64 >>> [job] = list(
65 ... getUtility(IInitialiseDistroSeriesJobSource).iterReady())
66 >>> job.distroseries == child
67 True
68
069
=== added file 'lib/lp/registry/tests/test_derivedistroseries.py'
--- lib/lp/registry/tests/test_derivedistroseries.py 1970-01-01 00:00:00 +0000
+++ lib/lp/registry/tests/test_derivedistroseries.py 2010-10-18 06:17:50 +0000
@@ -0,0 +1,76 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Test initialising a distroseries using
5IDistroSeries.deriveDistroSeries."""
6
7__metaclass__ = type
8
9from canonical.testing.layers import LaunchpadFunctionalLayer
10from lp.registry.interfaces.distroseries import DerivationError
11from lp.soyuz.interfaces.distributionjob import (
12 IInitialiseDistroSeriesJobSource,
13 )
14from lp.testing import (
15 login,
16 logout,
17 TestCaseWithFactory,
18 )
19from lp.testing.sampledata import ADMIN_EMAIL
20from zope.component import getUtility
21from zope.security.interfaces import Unauthorized
22
23
24class TestDeriveDistroSeries(TestCaseWithFactory):
25
26 layer = LaunchpadFunctionalLayer
27
28 def setUp(self):
29 super(TestDeriveDistroSeries, self).setUp()
30 self.soyuz = self.factory.makeTeam(name='soyuz-team')
31 self.parent = self.factory.makeDistroSeries()
32 self.child = self.factory.makeDistroSeries(
33 parent_series=self.parent)
34
35 def test_no_permission_to_call(self):
36 login(ADMIN_EMAIL)
37 person = self.factory.makePerson()
38 logout()
39 self.assertRaises(
40 Unauthorized, self.parent.deriveDistroSeries, person,
41 self.child.name)
42
43 def test_no_distroseries_and_no_arguments(self):
44 """Test that calling deriveDistroSeries() when the distroseries
45 doesn't exist, and not enough arguments are specified that the
46 function errors."""
47 self.assertRaisesWithContent(
48 DerivationError,
49 'Display Name needs to be set when creating a distroseries.',
50 self.parent.deriveDistroSeries, self.soyuz.teamowner,
51 'newdistro')
52
53 def test_parent_is_not_self(self):
54 other = self.factory.makeDistroSeries()
55 self.assertRaisesWithContent(
56 DerivationError,
57 "DistroSeries %s parent series isn't %s" % (
58 self.child.name, other.name),
59 other.deriveDistroSeries, self.soyuz.teamowner,
60 self.child.name)
61
62 def test_create_new_distroseries(self):
63 self.parent.deriveDistroSeries(
64 self.soyuz.teamowner, self.child.name)
65 [job] = list(
66 getUtility(IInitialiseDistroSeriesJobSource).iterReady())
67 self.assertEqual(job.distroseries, self.child)
68
69 def test_create_fully_new_distroseries(self):
70 self.parent.deriveDistroSeries(
71 self.soyuz.teamowner, 'deribuntu', displayname='Deribuntu',
72 title='The Deribuntu', summary='Deribuntu',
73 description='Deribuntu is great', version='11.11')
74 [job] = list(
75 getUtility(IInitialiseDistroSeriesJobSource).iterReady())
76 self.assertEqual(job.distroseries.name, 'deribuntu')
077
=== renamed file 'lib/canonical/launchpad/scripts/garbo.py' => 'lib/lp/scripts/garbo.py'
=== added directory 'lib/lp/scripts/tests'
=== added file 'lib/lp/scripts/tests/__init__.py'
=== renamed file 'lib/canonical/launchpad/scripts/tests/test_garbo.py' => 'lib/lp/scripts/tests/test_garbo.py'
--- lib/canonical/launchpad/scripts/tests/test_garbo.py 2010-10-03 15:30:06 +0000
+++ lib/lp/scripts/tests/test_garbo.py 2010-10-18 06:17:50 +0000
@@ -34,7 +34,7 @@
34from canonical.launchpad.database.openidconsumer import OpenIDConsumerNonce34from canonical.launchpad.database.openidconsumer import OpenIDConsumerNonce
35from canonical.launchpad.interfaces import IMasterStore35from canonical.launchpad.interfaces import IMasterStore
36from canonical.launchpad.interfaces.emailaddress import EmailAddressStatus36from canonical.launchpad.interfaces.emailaddress import EmailAddressStatus
37from canonical.launchpad.scripts.garbo import (37from lp.scripts.garbo import (
38 DailyDatabaseGarbageCollector,38 DailyDatabaseGarbageCollector,
39 HourlyDatabaseGarbageCollector,39 HourlyDatabaseGarbageCollector,
40 OpenIDConsumerAssociationPruner,40 OpenIDConsumerAssociationPruner,
4141
=== modified file 'lib/lp/scripts/utilities/importfascist.py'
--- lib/lp/scripts/utilities/importfascist.py 2010-09-03 04:14:41 +0000
+++ lib/lp/scripts/utilities/importfascist.py 2010-10-18 06:17:50 +0000
@@ -35,7 +35,7 @@
35 canonical.launchpad.feed.branch35 canonical.launchpad.feed.branch
36 lp.code.feed.branch36 lp.code.feed.branch
37 canonical.launchpad.interfaces.person37 canonical.launchpad.interfaces.person
38 canonical.launchpad.scripts.garbo38 lp.scripts.garbo
39 canonical.launchpad.vocabularies.dbobjects39 canonical.launchpad.vocabularies.dbobjects
40 lp.registry.vocabularies40 lp.registry.vocabularies
41 canonical.librarian.client41 canonical.librarian.client
4242
=== modified file 'lib/lp/soyuz/configure.zcml'
--- lib/lp/soyuz/configure.zcml 2010-10-06 18:53:53 +0000
+++ lib/lp/soyuz/configure.zcml 2010-10-18 06:17:50 +0000
@@ -905,9 +905,12 @@
905 provides="lp.soyuz.interfaces.distributionjob.IInitialiseDistroSeriesJobSource">905 provides="lp.soyuz.interfaces.distributionjob.IInitialiseDistroSeriesJobSource">
906 <allow interface="lp.soyuz.interfaces.distributionjob.IInitialiseDistroSeriesJobSource"/>906 <allow interface="lp.soyuz.interfaces.distributionjob.IInitialiseDistroSeriesJobSource"/>
907 </securedutility>907 </securedutility>
908908 <class class="lp.soyuz.model.distributionjob.DistributionJob">
909 <allow interface="lp.soyuz.interfaces.distributionjob.IDistributionJob" />
910 </class>
909 <class class="lp.soyuz.model.initialisedistroseriesjob.InitialiseDistroSeriesJob">911 <class class="lp.soyuz.model.initialisedistroseriesjob.InitialiseDistroSeriesJob">
910 <allow interface="lp.services.job.interfaces.job.IRunnableJob" />912 <allow interface="lp.soyuz.interfaces.distributionjob.IInitialiseDistroSeriesJob" />
913 <allow interface="lp.soyuz.interfaces.distributionjob.IDistributionJob" />
911 </class>914 </class>
912915
913</configure>916</configure>
914917
=== modified file 'lib/lp/soyuz/doc/sampledata-setup.txt'
--- lib/lp/soyuz/doc/sampledata-setup.txt 2010-08-13 02:59:14 +0000
+++ lib/lp/soyuz/doc/sampledata-setup.txt 2010-10-18 06:17:50 +0000
@@ -21,5 +21,5 @@
21 INFO ...21 INFO ...
22 INFO Done.22 INFO Done.
2323
24 >>> from canonical.launchpad.ftests.harness import LaunchpadTestSetup24 >>> from canonical.testing.layers import DatabaseLayer
25 >>> LaunchpadTestSetup().force_dirty_database()25 >>> DatabaseLayer.force_dirty_database()
2626
=== modified file 'lib/lp/soyuz/scripts/initialise_distroseries.py'
--- lib/lp/soyuz/scripts/initialise_distroseries.py 2010-10-14 12:56:31 +0000
+++ lib/lp/soyuz/scripts/initialise_distroseries.py 2010-10-18 06:17:50 +0000
@@ -16,7 +16,6 @@
16from canonical.launchpad.interfaces.lpstorm import IMasterStore16from canonical.launchpad.interfaces.lpstorm import IMasterStore
17from lp.buildmaster.enums import BuildStatus17from lp.buildmaster.enums import BuildStatus
18from lp.registry.interfaces.pocket import PackagePublishingPocket18from lp.registry.interfaces.pocket import PackagePublishingPocket
19from lp.registry.model.distroseries import DistroSeries
20from lp.soyuz.adapters.packagelocation import PackageLocation19from lp.soyuz.adapters.packagelocation import PackageLocation
21from lp.soyuz.enums import (20from lp.soyuz.enums import (
22 ArchivePurpose,21 ArchivePurpose,
@@ -61,7 +60,8 @@
6160
62 def __init__(61 def __init__(
63 self, distroseries, arches=(), packagesets=(), rebuild=False):62 self, distroseries, arches=(), packagesets=(), rebuild=False):
6463 # Avoid circular imports
64 from lp.registry.model.distroseries import DistroSeries
65 self.distroseries = distroseries65 self.distroseries = distroseries
66 self.parent = self.distroseries.parent_series66 self.parent = self.distroseries.parent_series
67 self.arches = arches67 self.arches = arches
6868
=== modified file 'lib/lp/soyuz/scripts/tests/test_buildd_cronscripts.py'
--- lib/lp/soyuz/scripts/tests/test_buildd_cronscripts.py 2010-10-04 19:50:45 +0000
+++ lib/lp/soyuz/scripts/tests/test_buildd_cronscripts.py 2010-10-18 06:17:50 +0000
@@ -74,9 +74,8 @@
74 rc, out, err = runner()74 rc, out, err = runner()
75 self.assertEqual(0, rc, "Err:\n%s" % err)75 self.assertEqual(0, rc, "Err:\n%s" % err)
7676
77 # 'runners' commit to the launchpad_ftest database in77 # 'runners' commit to the test database in subprocesses, so we need to
78 # subprocesses, so we need to tell the layer to fully78 # tell the layer to fully tear down and restore the database.
79 # tear down and restore the database.
80 DatabaseLayer.force_dirty_database()79 DatabaseLayer.force_dirty_database()
8180
82 return rc, out, err81 return rc, out, err
8382
=== modified file 'lib/lp/testing/__init__.py'
--- lib/lp/testing/__init__.py 2010-10-05 01:54:15 +0000
+++ lib/lp/testing/__init__.py 2010-10-18 06:17:50 +0000
@@ -325,20 +325,6 @@
325 transaction.commit()325 transaction.commit()
326 self.layer.switchDbUser(dbuser)326 self.layer.switchDbUser(dbuser)
327327
328 def installFixture(self, fixture):
329 """Install 'fixture', an object that has a `setUp` and `tearDown`.
330
331 `installFixture` will run 'fixture.setUp' and schedule
332 'fixture.tearDown' to be run during the test's tear down (using
333 `addCleanup`).
334
335 :param fixture: Any object that has a `setUp` and `tearDown` method.
336 :return: `fixture`.
337 """
338 fixture.setUp()
339 self.addCleanup(fixture.tearDown)
340 return fixture
341
342 def __str__(self):328 def __str__(self):
343 """The string representation of a test is its id.329 """The string representation of a test is its id.
344330
@@ -511,7 +497,7 @@
511 self.factory = ObjectFactory()497 self.factory = ObjectFactory()
512 # Record the oopses generated during the test run.498 # Record the oopses generated during the test run.
513 self.oopses = []499 self.oopses = []
514 self.installFixture(ZopeEventHandlerFixture(self._recordOops))500 self.useFixture(ZopeEventHandlerFixture(self._recordOops))
515 self.addCleanup(self.attachOopses)501 self.addCleanup(self.attachOopses)
516502
517 @adapter(ErrorReportEvent)503 @adapter(ErrorReportEvent)
518504
=== modified file 'lib/lp/testing/fixture.py'
--- lib/lp/testing/fixture.py 2010-08-20 20:31:18 +0000
+++ lib/lp/testing/fixture.py 2010-10-18 06:17:50 +0000
@@ -1,121 +1,25 @@
1# Copyright 2009, 2010 Canonical Ltd. This software is licensed under the1# Copyright 2009, 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4# pylint: disable-msg=E02114"""Launchpad test fixtures that have no better home."""
5
6"""Basic support for 'fixtures'.
7
8In this case, 'fixture' means an object that has a setUp and a tearDown
9method.
10"""
115
12__metaclass__ = type6__metaclass__ = type
13__all__ = [7__all__ = [
14 'Fixtures',8 'ZopeEventHandlerFixture',
15 'FixtureWithCleanup',
16 'IFixture',
17 'run_with_fixture',
18 'ServerFixture',
19 'with_fixture',
20 ]9 ]
2110
22from twisted.python.util import mergeFunctionMetadata11from fixtures import Fixture
23from zope.component import (12from zope.component import (
24 getGlobalSiteManager,13 getGlobalSiteManager,
25 provideHandler,14 provideHandler,
26 )15 )
27from zope.interface import (16
28 implements,17
29 Interface,18class ZopeEventHandlerFixture(Fixture):
30 )
31
32
33class IFixture(Interface):
34 """A fixture has a setUp and a tearDown method."""
35
36 def setUp():
37 """Set up the fixture."""
38
39 def tearDown():
40 """Tear down the fixture."""
41
42
43class FixtureWithCleanup:
44 """Fixture that allows arbitrary cleanup methods to be added.
45
46 Subclass this if you'd like to define a fixture that calls 'addCleanup'.
47 This is most often useful for fixtures that provide a way for users to
48 acquire resources arbitrarily.
49
50 Cleanups are run during 'tearDown' in reverse order to the order they were
51 added. If any of the cleanups raise an error, this error will be bubbled
52 up, causing tearDown to raise an exception, and the rest of the cleanups
53 will be run in a finally block.
54 """
55
56 implements(IFixture)
57
58 def setUp(self):
59 """See `IFixture`."""
60 self._cleanups = []
61
62 def _runCleanups(self):
63 if [] == self._cleanups:
64 return
65 f, args, kwargs = self._cleanups.pop()
66 try:
67 f(*args, **kwargs)
68 finally:
69 self._runCleanups()
70
71 def tearDown(self):
72 """See `IFixture`."""
73 self._runCleanups()
74
75 def addCleanup(self, function, *args, **kwargs):
76 """Run 'function' with arguments during tear down."""
77 self._cleanups.append((function, args, kwargs))
78
79
80class Fixtures(FixtureWithCleanup):
81 """A collection of `IFixture`s."""
82
83 def __init__(self, fixtures):
84 """Construct a fixture that groups many fixtures together.
85
86 :param fixtures: A list of `IFixture` objects.
87 """
88 self._fixtures = fixtures
89
90 def setUp(self):
91 super(Fixtures, self).setUp()
92 for fixture in self._fixtures:
93 fixture.setUp()
94 self.addCleanup(fixture.tearDown)
95
96
97def with_fixture(fixture):
98 """Decorate a function to run with a given fixture."""
99 def decorator(f):
100 def decorated(*args, **kwargs):
101 return run_with_fixture(fixture, f, fixture, *args, **kwargs)
102 return mergeFunctionMetadata(f, decorated)
103 return decorator
104
105
106def run_with_fixture(fixture, f, *args, **kwargs):
107 """Run `f` within the given `fixture`."""
108 try:
109 fixture.setUp()
110 return f(*args, **kwargs)
111 finally:
112 fixture.tearDown()
113
114
115class ZopeEventHandlerFixture(FixtureWithCleanup):
116 """A fixture that provides and then unprovides a Zope event handler."""19 """A fixture that provides and then unprovides a Zope event handler."""
11720
118 def __init__(self, handler):21 def __init__(self, handler):
22 super(ZopeEventHandlerFixture, self).__init__()
119 self._handler = handler23 self._handler = handler
12024
121 def setUp(self):25 def setUp(self):
@@ -123,18 +27,3 @@
123 gsm = getGlobalSiteManager()27 gsm = getGlobalSiteManager()
124 provideHandler(self._handler)28 provideHandler(self._handler)
125 self.addCleanup(gsm.unregisterHandler, self._handler)29 self.addCleanup(gsm.unregisterHandler, self._handler)
126
127
128class ServerFixture:
129 """Adapt a bzrlib `Server` into an `IFixture`."""
130
131 implements(IFixture)
132
133 def __init__(self, server):
134 self.server = server
135
136 def setUp(self):
137 self.server.start_server()
138
139 def tearDown(self):
140 self.server.stop_server()
14130
=== removed file 'lib/lp/testing/tests/test_fixture.py'
--- lib/lp/testing/tests/test_fixture.py 2010-08-20 20:31:18 +0000
+++ lib/lp/testing/tests/test_fixture.py 1970-01-01 00:00:00 +0000
@@ -1,138 +0,0 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for fixture support."""
5
6__metaclass__ = type
7
8import unittest
9
10from zope.interface import implements
11
12from lp.testing import TestCase
13from lp.testing.fixture import (
14 Fixtures,
15 FixtureWithCleanup,
16 IFixture,
17 run_with_fixture,
18 with_fixture,
19 )
20
21
22class LoggingFixture:
23
24 implements(IFixture)
25
26 def __init__(self, log):
27 self.log = log
28
29 def setUp(self):
30 self.log.append('setUp')
31
32 def tearDown(self):
33 self.log.append('tearDown')
34
35
36class TestFixture(TestCase):
37
38 def test_run_with_fixture(self):
39 # run_with_fixture runs the setUp method of the fixture, the passed
40 # function and then the tearDown method of the fixture.
41 log = []
42 fixture = LoggingFixture(log)
43 run_with_fixture(fixture, log.append, 'hello')
44 self.assertEqual(['setUp', 'hello', 'tearDown'], log)
45
46 def test_run_tearDown_even_with_exception(self):
47 # run_with_fixture runs the setUp method of the fixture, the passed
48 # function and then the tearDown method of the fixture even if the
49 # function raises an exception.
50 log = []
51 fixture = LoggingFixture(log)
52 self.assertRaises(
53 ZeroDivisionError, run_with_fixture, fixture, lambda: 1/0)
54 self.assertEqual(['setUp', 'tearDown'], log)
55
56 def test_with_fixture(self):
57 # with_fixture decorates a function so that it gets passed the fixture
58 # and the fixture is set up and torn down around the function.
59 log = []
60 fixture = LoggingFixture(log)
61 @with_fixture(fixture)
62 def function(fixture, **kwargs):
63 log.append(fixture)
64 log.append(kwargs)
65 return 'oi'
66 result = function(foo='bar')
67 self.assertEqual('oi', result)
68 self.assertEqual(['setUp', fixture, {'foo': 'bar'}, 'tearDown'], log)
69
70
71class TestFixtureWithCleanup(TestCase):
72 """Tests for `FixtureWithCleanup`."""
73
74 def test_cleanup_called_during_teardown(self):
75 log = []
76 fixture = FixtureWithCleanup()
77 fixture.setUp()
78 fixture.addCleanup(log.append, 'foo')
79 self.assertEqual([], log)
80 fixture.tearDown()
81 self.assertEqual(['foo'], log)
82
83 def test_cleanup_called_in_reverse_order(self):
84 log = []
85 fixture = FixtureWithCleanup()
86 fixture.setUp()
87 fixture.addCleanup(log.append, 'foo')
88 fixture.addCleanup(log.append, 'bar')
89 fixture.tearDown()
90 self.assertEqual(['bar', 'foo'], log)
91
92 def test_cleanup_run_even_in_failure(self):
93 log = []
94 fixture = FixtureWithCleanup()
95 fixture.setUp()
96 fixture.addCleanup(log.append, 'foo')
97 fixture.addCleanup(lambda: 1/0)
98 self.assertRaises(ZeroDivisionError, fixture.tearDown)
99 self.assertEqual(['foo'], log)
100
101
102class TestFixtures(TestCase):
103 """Tests the `Fixtures` class, which groups multiple `IFixture`s."""
104
105 class LoggingFixture:
106
107 def __init__(self, log):
108 self._log = log
109
110 def setUp(self):
111 self._log.append((self, 'setUp'))
112
113 def tearDown(self):
114 self._log.append((self, 'tearDown'))
115
116 def test_with_single_fixture(self):
117 log = []
118 a = self.LoggingFixture(log)
119 fixtures = Fixtures([a])
120 fixtures.setUp()
121 fixtures.tearDown()
122 self.assertEqual([(a, 'setUp'), (a, 'tearDown')], log)
123
124 def test_with_multiple_fixtures(self):
125 log = []
126 a = self.LoggingFixture(log)
127 b = self.LoggingFixture(log)
128 fixtures = Fixtures([a, b])
129 fixtures.setUp()
130 fixtures.tearDown()
131 self.assertEqual(
132 [(a, 'setUp'), (b, 'setUp'), (b, 'tearDown'), (a, 'tearDown')],
133 log)
134
135
136def test_suite():
137 return unittest.TestLoader().loadTestsFromName(__name__)
138
1390
=== modified file 'lib/lp/translations/doc/fix_translation_credits.txt'
--- lib/lp/translations/doc/fix_translation_credits.txt 2010-04-01 04:05:10 +0000
+++ lib/lp/translations/doc/fix_translation_credits.txt 2010-10-18 06:17:50 +0000
@@ -19,5 +19,5 @@
19After altering the database from a separate process, we must tell the19After altering the database from a separate process, we must tell the
20test setup that the database is dirty in spite of appearances.20test setup that the database is dirty in spite of appearances.
2121
22 >>> from canonical.launchpad.ftests.harness import LaunchpadTestSetup22 >>> from canonical.testing.layers import DatabaseLayer
23 >>> LaunchpadTestSetup().force_dirty_database()23 >>> DatabaseLayer.force_dirty_database()
2424
=== modified file 'lib/lp/translations/doc/message-sharing-merge-script.txt'
--- lib/lp/translations/doc/message-sharing-merge-script.txt 2009-08-04 13:37:57 +0000
+++ lib/lp/translations/doc/message-sharing-merge-script.txt 2010-10-18 06:17:50 +0000
@@ -20,5 +20,5 @@
20# The script modified the database, even though the database layer may20# The script modified the database, even though the database layer may
21# not have noticed it.21# not have noticed it.
2222
23 >>> from canonical.launchpad.ftests.harness import LaunchpadTestSetup23 >>> from canonical.testing.layers import DatabaseLayer
24 >>> LaunchpadTestSetup().force_dirty_database()24 >>> DatabaseLayer.force_dirty_database()
2525
=== modified file 'lib/lp/translations/doc/request_country.txt'
--- lib/lp/translations/doc/request_country.txt 2010-02-26 21:58:15 +0000
+++ lib/lp/translations/doc/request_country.txt 2010-10-18 06:17:50 +0000
@@ -4,10 +4,6 @@
44
5Adapting a request to a country allows you to see where the request came from.5Adapting a request to a country allows you to see where the request came from.
66
7 >>> from canonical.launchpad.ftests.harness import (
8 ... LaunchpadFunctionalTestSetup)
9 >>> LaunchpadFunctionalTestSetup().setUp()
10
11Here's a dummy request. Zope adds the REMOTE_ADDR CGI environment variable7Here's a dummy request. Zope adds the REMOTE_ADDR CGI environment variable
12for us. Upstream proxy servers (and tinkering users!) may also add8for us. Upstream proxy servers (and tinkering users!) may also add
13X-Forwarded-For: headers. The X-Forwarded-For: header takes precidence9X-Forwarded-For: headers. The X-Forwarded-For: header takes precidence
@@ -34,6 +30,3 @@
34 Traceback (most recent call last):30 Traceback (most recent call last):
35 ...31 ...
36 TypeError: ('Could not adapt', ...32 TypeError: ('Could not adapt', ...
37
38 >>> LaunchpadFunctionalTestSetup().tearDown()
39

Subscribers

People subscribed via source and target branches

to status/vote changes: