Merge ~cjwatson/launchpad:improve-config-fixture into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 32738952c1a0eebd7423654da367a1acb5faa0dc
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:improve-config-fixture
Merge into: launchpad:master
Diff against target: 306 lines (+183/-16)
5 files modified
lib/lp/services/config/fixture.py (+83/-10)
lib/lp/services/config/tests/test_fixture.py (+87/-1)
lib/lp/services/librarianserver/testing/server.py (+2/-0)
lib/lp/testing/layers.py (+8/-5)
lib/lp/testing/swift/fixture.py (+3/-0)
Reviewer Review Type Date Requested Status
Ioana Lasc (community) Approve
Review via email: mp+404849@code.launchpad.net

Commit message

Allow removing sections from ConfigFixture

Description of the change

This makes it possible to tear down some configuration that couldn't previously be torn down properly.

To post a comment you must log in.
Revision history for this message
Ioana Lasc (ilasc) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/services/config/fixture.py b/lib/lp/services/config/fixture.py
2index d79fe37..10426e4 100644
3--- a/lib/lp/services/config/fixture.py
4+++ b/lib/lp/services/config/fixture.py
5@@ -1,15 +1,13 @@
6 # Copyright 2010-2018 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9-"""Fixtures related to configs.
10-
11-XXX: Robert Collins 2010-10-20 bug=663454 this is in the wrong namespace.
12-"""
13+"""Fixtures related to configs."""
14
15 __metaclass__ = type
16
17 __all__ = [
18 'ConfigFixture',
19+ 'ConfigMismatchError',
20 'ConfigUseFixture',
21 ]
22
23@@ -19,10 +17,16 @@ from textwrap import dedent
24
25 from fixtures import Fixture
26 import scandir
27+import six
28+from six.moves.configparser import RawConfigParser
29
30 from lp.services.config import config
31
32
33+class ConfigMismatchError(Exception):
34+ """Removing configuration failed due to a mismatch in expected contents."""
35+
36+
37 class ConfigFixture(Fixture):
38 """Create a unique launchpad config."""
39
40@@ -40,15 +44,84 @@ class ConfigFixture(Fixture):
41 self.instance_name = instance_name
42 self.copy_from_instance = copy_from_instance
43
44- def add_section(self, sectioncontent):
45- """Add sectioncontent to the lazy config."""
46- with open(self.absroot + '/launchpad-lazr.conf', 'a') as out:
47- out.write(sectioncontent)
48- # Trigger a refresh if and only if the config is in use at the moment
49- # in order to make these new values available.
50+ def _parseConfigData(self, conf_data, conf_filename):
51+ """Parse a single chunk of config data, with no inheritance."""
52+ # Compare https://bugs.launchpad.net/lazr.config/+bug/1397779.
53+ parser_kws = {}
54+ if six.PY3:
55+ parser_kws['strict'] = False
56+ parser = RawConfigParser(**parser_kws)
57+ parser.readfp(six.StringIO(conf_data), conf_filename)
58+ return parser
59+
60+ def _parseConfigFile(self, conf_filename):
61+ """Parse a single config file, with no inheritance."""
62+ if os.path.exists(conf_filename):
63+ with open(conf_filename) as conf_file:
64+ conf_data = conf_file.read()
65+ else:
66+ conf_data = ''
67+ return self._parseConfigData(conf_data, conf_filename)
68+
69+ def _writeConfigFile(self, parser, conf_filename):
70+ """Write a parsed config to a file."""
71+ with open(conf_filename, 'w') as conf_file:
72+ for i, section in enumerate(parser.sections()):
73+ if i:
74+ conf_file.write('\n')
75+ conf_file.write('[%s]\n' % section)
76+ for key, value in parser.items(section):
77+ conf_file.write(
78+ '%s: %s\n' % (key, str(value).replace('\n', '\n\t')))
79+
80+ def _refresh(self):
81+ """Trigger a config refresh if necessary.
82+
83+ If and only if the config is in use at the moment, we need to
84+ refresh in order to make changes available.
85+ """
86 if config.instance_name == self.instance_name:
87 config._invalidateConfig()
88
89+ def add_section(self, sectioncontent):
90+ """Add sectioncontent to the lazr config."""
91+ conf_filename = os.path.join(self.absroot, 'launchpad-lazr.conf')
92+ parser = self._parseConfigFile(conf_filename)
93+ add_parser = self._parseConfigData(
94+ sectioncontent, '<configuration to add>')
95+ for section in add_parser.sections():
96+ if not parser.has_section(section):
97+ parser.add_section(section)
98+ for name, value in add_parser.items(section):
99+ parser.set(section, name, value)
100+ self._writeConfigFile(parser, conf_filename)
101+ self._refresh()
102+
103+ def remove_section(self, sectioncontent):
104+ """Remove sectioncontent from the lazr config."""
105+ conf_filename = os.path.join(self.absroot, 'launchpad-lazr.conf')
106+ parser = self._parseConfigFile(conf_filename)
107+ remove_parser = self._parseConfigData(
108+ sectioncontent, '<configuration to remove>')
109+ for section in remove_parser.sections():
110+ if not parser.has_section(section):
111+ continue
112+ for name, value in remove_parser.items(section):
113+ if not parser.has_option(section, name):
114+ continue
115+ current_value = parser.get(section, name)
116+ if value != current_value:
117+ raise ConfigMismatchError(
118+ "Can't remove %s.%s option from %s: "
119+ "expected value '%s', current value '%s'" % (
120+ section, name, conf_filename,
121+ value, current_value))
122+ parser.remove_option(section, name)
123+ if not parser.options(section):
124+ parser.remove_section(section)
125+ self._writeConfigFile(parser, conf_filename)
126+ self._refresh()
127+
128 def _setUp(self):
129 root = os.path.join(config.root, 'configs', self.instance_name)
130 os.mkdir(root)
131diff --git a/lib/lp/services/config/tests/test_fixture.py b/lib/lp/services/config/tests/test_fixture.py
132index 7db44b4..639d292 100644
133--- a/lib/lp/services/config/tests/test_fixture.py
134+++ b/lib/lp/services/config/tests/test_fixture.py
135@@ -5,13 +5,16 @@
136
137 __metaclass__ = type
138
139-from testtools import TestCase
140+import os.path
141+from textwrap import dedent
142
143 from lp.services.config import config
144 from lp.services.config.fixture import (
145 ConfigFixture,
146+ ConfigMismatchError,
147 ConfigUseFixture,
148 )
149+from lp.testing import TestCase
150
151
152 class TestConfigUseFixture(TestCase):
153@@ -54,3 +57,86 @@ class TestConfigFixture(TestCase):
154 lazr_config.strip())
155 finally:
156 fixture.cleanUp()
157+
158+ def test_add_and_remove_section(self):
159+ fixture = ConfigFixture('testtestconfig', 'testrunner')
160+ fixture.setUp()
161+ try:
162+ confpath = 'configs/testtestconfig/launchpad-lazr.conf'
163+ with open(confpath) as f:
164+ lazr_config = f.read()
165+ self.assertEqual(dedent("""\
166+ [meta]
167+ extends: ../testrunner/launchpad-lazr.conf
168+ """), lazr_config)
169+
170+ fixture.add_section(dedent("""\
171+ [test1]
172+ key: false
173+ """))
174+ with open(confpath) as f:
175+ lazr_config = f.read()
176+ self.assertEqual(dedent("""\
177+ [meta]
178+ extends: ../testrunner/launchpad-lazr.conf
179+
180+ [test1]
181+ key: false
182+ """), lazr_config)
183+
184+ fixture.add_section(dedent("""\
185+ [test2]
186+ key: true
187+ """))
188+ with open(confpath) as f:
189+ lazr_config = f.read()
190+ self.assertEqual(dedent("""\
191+ [meta]
192+ extends: ../testrunner/launchpad-lazr.conf
193+
194+ [test1]
195+ key: false
196+
197+ [test2]
198+ key: true
199+ """), lazr_config)
200+
201+ fixture.remove_section(dedent("""\
202+ [test1]
203+ key: false
204+ """))
205+ with open(confpath) as f:
206+ lazr_config = f.read()
207+ self.assertEqual(dedent("""\
208+ [meta]
209+ extends: ../testrunner/launchpad-lazr.conf
210+
211+ [test2]
212+ key: true
213+ """), lazr_config)
214+ finally:
215+ fixture.cleanUp()
216+
217+ def test_remove_section_unexpected_value(self):
218+ fixture = ConfigFixture('testtestconfig', 'testrunner')
219+ fixture.setUp()
220+ try:
221+ confpath = os.path.abspath(
222+ 'configs/testtestconfig/launchpad-lazr.conf')
223+
224+ fixture.add_section(dedent("""\
225+ [test1]
226+ key: false
227+ """))
228+
229+ self.assertRaisesWithContent(
230+ ConfigMismatchError,
231+ "Can't remove test1.key option from %s: "
232+ "expected value 'true', current value 'false'" % confpath,
233+ fixture.remove_section,
234+ dedent("""\
235+ [test1]
236+ key: true
237+ """))
238+ finally:
239+ fixture.cleanUp()
240diff --git a/lib/lp/services/librarianserver/testing/server.py b/lib/lp/services/librarianserver/testing/server.py
241index 12c50d6..20bfdfd 100644
242--- a/lib/lp/services/librarianserver/testing/server.py
243+++ b/lib/lp/services/librarianserver/testing/server.py
244@@ -92,6 +92,8 @@ class LibrarianServerFixture(TacTestSetup):
245 warnings.warn("Attempt to tearDown inactive fixture.",
246 DeprecationWarning, stacklevel=3)
247 return
248+ self.config_fixture.remove_section(self.service_config)
249+ config.reloadConfig()
250 TacTestSetup.cleanUp(self)
251
252 def clear(self):
253diff --git a/lib/lp/testing/layers.py b/lib/lp/testing/layers.py
254index ebd1f96..3b72c73 100644
255--- a/lib/lp/testing/layers.py
256+++ b/lib/lp/testing/layers.py
257@@ -663,11 +663,12 @@ class RabbitMQLayer(BaseLayer):
258 def tearDown(cls):
259 if not cls._is_setup:
260 return
261+ cls.appserver_config_fixture.remove_section(
262+ cls.rabbit.config.service_config)
263+ cls.config_fixture.remove_section(
264+ cls.rabbit.config.service_config)
265 cls.rabbit.cleanUp()
266 cls._is_setup = False
267- # Can't pop the config above, so bail here and let the test runner
268- # start a sub-process.
269- raise NotImplementedError
270
271 @classmethod
272 @profiled
273@@ -795,8 +796,8 @@ class LibrarianLayer(DatabaseLayer):
274
275 # Make sure things using the appserver config know the
276 # correct Librarian port numbers.
277- cls.appserver_config_fixture.add_section(
278- cls.librarian_fixture.service_config)
279+ cls.appserver_service_config = cls.librarian_fixture.service_config
280+ cls.appserver_config_fixture.add_section(cls.appserver_service_config)
281
282 @classmethod
283 @profiled
284@@ -805,6 +806,8 @@ class LibrarianLayer(DatabaseLayer):
285 # responsibilities : not desirable though.
286 if cls.librarian_fixture is None:
287 return
288+ cls.appserver_config_fixture.remove_section(
289+ cls.appserver_service_config)
290 try:
291 cls._check_and_reset()
292 finally:
293diff --git a/lib/lp/testing/swift/fixture.py b/lib/lp/testing/swift/fixture.py
294index 3359380..2158b29 100644
295--- a/lib/lp/testing/swift/fixture.py
296+++ b/lib/lp/testing/swift/fixture.py
297@@ -67,6 +67,9 @@ class SwiftFixture(TacTestFixture):
298 fakeswift.DEFAULT_PASSWORD, fakeswift.DEFAULT_TENANT_NAME))
299 BaseLayer.config_fixture.add_section(service_config)
300 config.reloadConfig()
301+ self.addCleanup(config.reloadConfig)
302+ self.addCleanup(
303+ BaseLayer.config_fixture.remove_section, service_config)
304 assert config.librarian_server.os_tenant_name == 'test'
305
306 def setUpRoot(self):

Subscribers

People subscribed via source and target branches

to status/vote changes: