Merge lp:~jimbaker/pyjuju/zk-test-mgmt into lp:pyjuju
- zk-test-mgmt
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 93 | ||||
Proposed branch: | lp:~jimbaker/pyjuju/zk-test-mgmt | ||||
Merge into: | lp:pyjuju | ||||
Diff against target: |
576 lines (+206/-30) 16 files modified
ensemble/__init__.py (+1/-0) ensemble/agents/tests/common.py (+2/-1) ensemble/agents/tests/test_base.py (+7/-5) ensemble/agents/tests/test_machine.py (+4/-2) ensemble/control/initialize.py (+5/-1) ensemble/formula/tests/test_publisher.py (+2/-1) ensemble/ftests/test_connection.py (+3/-3) ensemble/machine/tests/test_unit_deployment.py (+7/-5) ensemble/providers/dummy.py (+2/-2) ensemble/providers/ec2/tests/test_connect.py (+2/-1) ensemble/providers/ec2/tests/test_launch.py (+2/-1) ensemble/providers/tests/test_dummy.py (+1/-2) ensemble/state/tests/common.py (+3/-2) ensemble/state/tests/test_initialize.py (+2/-1) ensemble/tests/common.py (+152/-0) test (+11/-3) |
||||
To merge this branch: | bzr merge lp:~jimbaker/pyjuju/zk-test-mgmt | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gustavo Niemeyer | Approve | ||
Review via email: mp+40160@code.launchpad.net |
Commit message
Description of the change
Manage the running of a test ZooKeeper instance during Twisted Trial execution:
- Create temp directory with all required files for ZooKeeper to run
- Work with either dev build or release versions of ZooKeeper
- Using subprocess, start ZooKeeper, then terminate and cleanup upon
the end of a test
Modified the following code:
- test, via the use of a context manager that encapsulates the actual Trial run
- ensemble/
- unit tests and classes referring to "2181" in their source code
Fixes #641482
Jim Baker (jimbaker) wrote : | # |
On Fri, Nov 5, 2010 at 11:51 AM, Gustavo Niemeyer <email address hidden>wrote:
> Review: Needs Fixing
> Hey Jim,
>
> Here are some preliminary comments:
>
> [1]
>
> +"""This port can be overriden when testing"""
> +ZOOKEEPER_PORT = 2181
+
> +"""For convenience, this should be kept synchronized with the
> ZOOKEEPER_PORT"""
> +ZOOKEEPER_
>
>
> We've managed to avoid global variables thus far, and it'd
> be nice if we could manage to maintain things that way. It's
> fine to have these defined for tests, though, but this is
> sitting outside of the test environment.
>
This is one reason it took a while to do this, but it seems better than the
alternative, which is to have "127.0.0.1:2181" (and variants) scattered
through our code.
>
> Also, this is minor, since we're talking about tests, but this
> redundancy feels a bit strange. You can just define this latter
> one and very easily obtain the port number out of it. That said,
> the port number isn't even used anywhere, so let's just get rid
> of it entirely.
>
Agreed port number is not currently being used. I was of two minds on this
because it seemed potentially useful for the future. Having one derived from
the other would be nicer, but property doesn't work on module-level names,
and spending engineering on it to wrap in a class seemed silly. Probably
best to remove.
>
> +# some standard dependency injection in the Java parlance we need to do
> +# to override our normal assumptions so we can test
>
> As a random comment, dependency injection is somewhat like
> the opposite of a global variable. :-)
>
Yeah, except in Python, being able to just change it is something like DI.
There's no need to write an accessor when you can just set an attribute, so
it works the same. (Real DI of course allows for more sophisticated sense of
context, but in this case global context seems to be just right.)
>
> [2]
>
> The branch is missing some pieces. zookeeper_
> specifically.
>
This function is in ensemble.
branch.
> --
> https:/
> You are the owner of lp:~jimbaker/ensemble/zk-test-mgmt.
>
Gustavo Niemeyer (niemeyer) wrote : | # |
[1]
> This is one reason it took a while to do this, but it seems better than the
> alternative, which is to have "127.0.0.1:2181" (and variants) scattered
> through our code.
It's better to have an internal default value which is used by the specific class
which connects to zookeeper, than having a public value like that which is used
through out the code and directly modified by clients, for all the reasons why
global variables are bad (common state rather than local, side effects across
the board, etc).
If you want to define that for tests only, then that certainly feels ok, but can
we please have that under the test namespace rather than the main package one?
> Agreed port number is not currently being used. I was of two minds on this
> because it seemed potentially useful for the future. Having one derived from
> the other would be nicer, but property doesn't work on module-level names,
> and spending engineering on it to wrap in a class seemed silly. Probably
> best to remove.
Thanks!
> > As a random comment, dependency injection is somewhat like
> > the opposite of a global variable. :-)
>
> Yeah, except in Python, being able to just change it is something like DI.
This is a quite orthogonal point to the whole review, but having a global
variable like this is pretty much the same thing as having a "public static"
member in an "Ensemble" class, which (other :-) Java advocates won't really
buy as DI by itself.
> There's no need to write an accessor when you can just set an attribute, so
> it works the same. (Real DI of course allows for more sophisticated sense of
> context, but in this case global context seems to be just right.)
Global state is bad in pretty much all cases. Imagine that we want to connect
to two different servers to interconnect them. We want to be able to build
model objects with local state, that do not depend on a unique source of
information for the server address.
Dependency injection might fail in this case as well, in fact, which is one
of the reasons why I'm not a big fan of it either. The context should
generally be provided to the objects on an instance basis, rather than
having objects configured on a class level.
[2]
Yeah, I'm sorry about this. I've screwed it up locally. Will follow up with
the continuation shortly.
Gustavo Niemeyer (niemeyer) wrote : | # |
That looks awesome, thanks a lot!
In addition to the above, have only one significant comment, and
two minors. +1 one considering these.
[3]
I have two test failures within the branch, but all tests pass with trunk.
I've pasted the errors to: https:/
[4]
+dataDir=%s
+clientPort=%s
+""" % (os.path.
It'd probably safer to put the data under a subdirectory of working_path,
as you have done for logs. This will avoid any potential conflicts, and
isolate the various data files in a single place.
[5]
+ with zookeeper_
+ os.environ[
+ os.environ.
+ run()
As a silly minor, would you mind to indent the second and third lines in by
another 4 spaces or something similar? It feels more readable to not have
the statement continuation aligned with the actual block content.
Jim Baker (jimbaker) wrote : | # |
On Fri, Nov 5, 2010 at 2:29 PM, Gustavo Niemeyer <email address hidden>wrote:
> Review: Approve
> That looks awesome, thanks a lot!
>
> I will commit to trunk momentarily, see my other comments.
> In addition to the above, have only one significant comment, and
> two minors. +1 one considering these.
>
> [3]
>
> I have two test failures within the branch, but all tests pass with trunk.
>
> I've pasted the errors to: https:/
These errors occasionally happen on trunk checkout too. They should be fixed
of course, but not related specifically to this branch.
>
>
> [4]
>
> +dataDir=%s
> +clientPort=%s
> +""" % (os.path.
>
> It'd probably safer to put the data under a subdirectory of working_path,
> as you have done for logs. This will avoid any potential conflicts, and
> isolate the various data files in a single place.
>
Actually this is a separate subdirectory, but I have made clearer in the
current code its setup.
>
> [5]
>
> + with zookeeper_
> + os.environ[
> + os.environ.
> + run()
>
> As a silly minor, would you mind to indent the second and third lines in by
> another 4 spaces or something similar? It feels more readable to not have
> the statement continuation aligned with the actual block content.
>
Done.
>
> --
> https:/
> You are the owner of lp:~jimbaker/ensemble/zk-test-mgmt.
>
Jim Baker (jimbaker) wrote : | # |
On Fri, Nov 5, 2010 at 2:08 PM, Gustavo Niemeyer <email address hidden>wrote:
> Review: Needs Fixing
> [1]
>
> > This is one reason it took a while to do this, but it seems better than
> the
> > alternative, which is to have "127.0.0.1:2181" (and variants) scattered
> > through our code.
>
> It's better to have an internal default value which is used by the specific
> class
> which connects to zookeeper, than having a public value like that which is
> used
> through out the code and directly modified by clients, for all the reasons
> why
> global variables are bad (common state rather than local, side effects
> across
> the board, etc).
>
> If you want to define that for tests only, then that certainly feels ok,
> but can
> we please have that under the test namespace rather than the main package
> one?
>
I have move the address lookup to
ensemble.
instead driven by an optional new env var, ZOOKEEPER_ADDRESS. This is set by
the test process, and is used in two places in non-test code:
1. In the construction of the client in ensemble.
then passed down as in trunk
2. In ensemble.
just test code too, just put in parallel)
>
> > Agreed port number is not currently being used. I was of two minds on
> this
> > because it seemed potentially useful for the future. Having one derived
> from
> > the other would be nicer, but property doesn't work on module-level
> names,
> > and spending engineering on it to wrap in a class seemed silly. Probably
> > best to remove.
>
> Thanks!
>
>
Done
Preview Diff
1 | === modified file 'ensemble/__init__.py' |
2 | --- ensemble/__init__.py 2010-05-04 14:11:23 +0000 |
3 | +++ ensemble/__init__.py 2010-11-08 23:03:47 +0000 |
4 | @@ -0,0 +1,1 @@ |
5 | +# |
6 | |
7 | === modified file 'ensemble/agents/tests/common.py' |
8 | --- ensemble/agents/tests/common.py 2010-09-20 19:53:57 +0000 |
9 | +++ ensemble/agents/tests/common.py 2010-11-08 23:03:47 +0000 |
10 | @@ -7,6 +7,7 @@ |
11 | from ensemble.agents.base import TwistedOptionNamespace |
12 | from ensemble.environment.config import EnvironmentsConfig |
13 | from ensemble.state.tests.common import StateTestBase |
14 | +from ensemble.tests.common import get_test_zookeeper_address |
15 | |
16 | |
17 | SAMPLE_ENV = """\ |
18 | @@ -52,7 +53,7 @@ |
19 | |
20 | def get_agent_config(self): |
21 | options = TwistedOptionNamespace() |
22 | - options["zookeeper_servers"] = "localhost:2181" |
23 | + options["zookeeper_servers"] = get_test_zookeeper_address() |
24 | return succeed(options) |
25 | |
26 | @inlineCallbacks |
27 | |
28 | === modified file 'ensemble/agents/tests/test_base.py' |
29 | --- ensemble/agents/tests/test_base.py 2010-09-15 19:53:52 +0000 |
30 | +++ ensemble/agents/tests/test_base.py 2010-11-08 23:03:47 +0000 |
31 | @@ -10,11 +10,13 @@ |
32 | from txzookeeper import ZookeeperClient |
33 | from ensemble.lib.testing import TestCase |
34 | from ensemble.lib.mocker import MATCH |
35 | +from ensemble.tests.common import get_test_zookeeper_address |
36 | |
37 | from ensemble.agents.base import ( |
38 | BaseAgent, TwistedOptionNamespace, AgentRunner) |
39 | from ensemble.errors import NoConnection |
40 | |
41 | + |
42 | MATCH_APP = MATCH(lambda x: isinstance(x, Componentized)) |
43 | |
44 | |
45 | @@ -105,13 +107,13 @@ |
46 | This will create an agent instance, parse the cli args, passes them to |
47 | the agent, and starts the agent runner. |
48 | """ |
49 | - self.change_args("es-agent", "--zookeeper-servers", "localhost:2181") |
50 | + self.change_args("es-agent", "--zookeeper-servers", get_test_zookeeper_address()) |
51 | runner = self.mocker.patch(AgentRunner) |
52 | runner.run() |
53 | mock_agent = self.mocker.patch(BaseAgent) |
54 | |
55 | def match_args(config): |
56 | - self.assertEqual(config["zookeeper_servers"], "localhost:2181") |
57 | + self.assertEqual(config["zookeeper_servers"], get_test_zookeeper_address()) |
58 | return True |
59 | |
60 | mock_agent.configure(MATCH(match_args)) |
61 | @@ -143,7 +145,7 @@ |
62 | |
63 | pid_file = self.makeFile() |
64 | self.change_args( |
65 | - "es-agent", "--zookeeper-servers", "localhost:2181", |
66 | + "es-agent", "--zookeeper-servers", get_test_zookeeper_address(), |
67 | "--pidfile", pid_file) |
68 | runner = self.mocker.patch(AgentRunner) |
69 | logger = self.mocker.patch(AppLogger) |
70 | @@ -218,13 +220,13 @@ |
71 | def test_run_base_raises_error(self): |
72 | """The base class agent, raises a notimplemented error when started.""" |
73 | client = self.mocker.patch(ZookeeperClient) |
74 | - client.connect("localhost:2181") |
75 | + client.connect(get_test_zookeeper_address()) |
76 | client_mock = self.mocker.mock() |
77 | self.mocker.result(succeed(client_mock)) |
78 | self.mocker.replay() |
79 | |
80 | agent = BaseAgent() |
81 | - agent.configure({"zookeeper_servers": "localhost:2181"}) |
82 | + agent.configure({"zookeeper_servers": get_test_zookeeper_address()}) |
83 | d = agent.startService() |
84 | self.failUnlessFailure(d, NotImplementedError) |
85 | return d |
86 | |
87 | === modified file 'ensemble/agents/tests/test_machine.py' |
88 | --- ensemble/agents/tests/test_machine.py 2010-10-14 19:54:48 +0000 |
89 | +++ ensemble/agents/tests/test_machine.py 2010-11-08 23:03:47 +0000 |
90 | @@ -17,6 +17,8 @@ |
91 | from ensemble.state.environment import EnvironmentStateManager |
92 | from ensemble.state.machine import MachineStateManager, MachineState |
93 | from ensemble.state.service import ServiceStateManager |
94 | +from ensemble.tests.common import get_test_zookeeper_address |
95 | + |
96 | |
97 | from .common import AgentTestBase |
98 | |
99 | @@ -169,7 +171,7 @@ |
100 | yield self.agent.start() |
101 | |
102 | mock_deployment = self.mocker.patch(ServiceUnitDeployment) |
103 | - mock_deployment.start("0", "localhost:2181") |
104 | + mock_deployment.start("0", get_test_zookeeper_address()) |
105 | |
106 | test_deferred = Deferred() |
107 | |
108 | @@ -225,7 +227,7 @@ |
109 | test_deferred = Deferred() |
110 | |
111 | mock_deployment = self.mocker.patch(ServiceUnitDeployment) |
112 | - mock_deployment.start("0", "localhost:2181") |
113 | + mock_deployment.start("0", get_test_zookeeper_address()) |
114 | self.mocker.result(succeed(True)) |
115 | mock_deployment.destroy() |
116 | |
117 | |
118 | === modified file 'ensemble/control/initialize.py' |
119 | --- ensemble/control/initialize.py 2010-09-22 20:28:49 +0000 |
120 | +++ ensemble/control/initialize.py 2010-11-08 23:03:47 +0000 |
121 | @@ -1,3 +1,5 @@ |
122 | +import os |
123 | + |
124 | from twisted.internet.defer import inlineCallbacks |
125 | from twisted.internet import reactor |
126 | |
127 | @@ -22,7 +24,9 @@ |
128 | """ |
129 | Initialize Zookeeper hierarchy |
130 | """ |
131 | - client = yield ZookeeperClient("localhost:2181").connect() |
132 | + zk_address = os.environ.get("ZOOKEEPER_ADDRESS", "127.0.0.1:2181") |
133 | + print "Using ZK address", zk_address |
134 | + client = yield ZookeeperClient(zk_address).connect() |
135 | try: |
136 | hierarchy = StateHierarchy( |
137 | client, options.admin_identity, options.instance_id) |
138 | |
139 | === modified file 'ensemble/formula/tests/test_publisher.py' |
140 | --- ensemble/formula/tests/test_publisher.py 2010-10-14 16:08:48 +0000 |
141 | +++ ensemble/formula/tests/test_publisher.py 2010-11-08 23:03:47 +0000 |
142 | @@ -14,6 +14,7 @@ |
143 | from ensemble.providers.dummy import FileStorage |
144 | from ensemble.state.formula import FormulaStateManager |
145 | from ensemble.state.errors import StateChanged |
146 | +from ensemble.tests.common import get_test_zookeeper_address |
147 | |
148 | from ensemble.environment.tests.test_config import ( |
149 | EnvironmentsConfigTestBase, SAMPLE_ENV) |
150 | @@ -37,7 +38,7 @@ |
151 | self.formula_storage_key = "%s:%s" % ( |
152 | self.formula_key, self.formula.get_sha256()) |
153 | |
154 | - self.client = ZookeeperClient("127.0.0.1:2181") |
155 | + self.client = ZookeeperClient(get_test_zookeeper_address()) |
156 | self.storage = FileStorage(self.makeDir()) |
157 | self.publisher = FormulaPublisher(self.client, self.storage) |
158 | |
159 | |
160 | === modified file 'ensemble/ftests/test_connection.py' |
161 | --- ensemble/ftests/test_connection.py 2010-09-29 20:31:59 +0000 |
162 | +++ ensemble/ftests/test_connection.py 2010-11-08 23:03:47 +0000 |
163 | @@ -12,10 +12,10 @@ |
164 | import pwd |
165 | import zookeeper |
166 | |
167 | - |
168 | from ensemble.errors import NoConnection |
169 | from ensemble.lib.testing import TestCase |
170 | from ensemble.state.sshclient import SSHClient |
171 | +from ensemble.tests.common import get_test_zookeeper_address |
172 | |
173 | |
174 | class ConnectionTest(TestCase): |
175 | @@ -39,7 +39,7 @@ |
176 | Forwarding a port spawns an ssh process with port forwarding arguments. |
177 | """ |
178 | connect_deferred = self.client.connect( |
179 | - "localhost:2181", timeout=20) |
180 | + get_test_zookeeper_address(), timeout=20) |
181 | |
182 | def validate_connected(client): |
183 | self.assertTrue(client.connected) |
184 | @@ -80,7 +80,7 @@ |
185 | """ |
186 | SSHClient.remote_user = "rabbit" |
187 | connect_deferred = self.client.connect( |
188 | - "localhost:2181", timeout=10) |
189 | + get_test_zookeeper_address(), timeout=10) |
190 | self.failUnlessFailure(connect_deferred, NoConnection) |
191 | |
192 | def validate_log(result): |
193 | |
194 | === modified file 'ensemble/machine/tests/test_unit_deployment.py' |
195 | --- ensemble/machine/tests/test_unit_deployment.py 2010-11-02 16:27:50 +0000 |
196 | +++ ensemble/machine/tests/test_unit_deployment.py 2010-11-08 23:03:47 +0000 |
197 | @@ -15,6 +15,8 @@ |
198 | from ensemble.lib.mocker import MATCH |
199 | from ensemble.machine.unit import ServiceUnitDeployment |
200 | from ensemble.machine.errors import UnitDeploymentError |
201 | +from ensemble.tests.common import get_test_zookeeper_address |
202 | + |
203 | |
204 | MATCH_PROTOCOL = MATCH(lambda x: isinstance(x, ProcessProtocol)) |
205 | |
206 | @@ -67,7 +69,7 @@ |
207 | if it does not exist and a running service unit agent. |
208 | """ |
209 | self.deployment.unpack_formula(self.bundle) |
210 | - d = self.deployment.start("0", "localhost:2181") |
211 | + d = self.deployment.start("0", get_test_zookeeper_address()) |
212 | |
213 | @inlineCallbacks |
214 | def validate_result(result): |
215 | @@ -87,7 +89,7 @@ |
216 | if it does not exist and a running service unit agent. |
217 | """ |
218 | self.deployment.unpack_formula(self.bundle) |
219 | - d = self.deployment.start(21, "localhost:2181") |
220 | + d = self.deployment.start(21, get_test_zookeeper_address()) |
221 | |
222 | @inlineCallbacks |
223 | def validate_result(result): |
224 | @@ -108,7 +110,7 @@ |
225 | """ |
226 | self.deployment.unit_agent_module = "magichat.xr1" |
227 | self.deployment.unpack_formula(self.bundle) |
228 | - d = self.deployment.start("0", "localhost:2181") |
229 | + d = self.deployment.start("0", get_test_zookeeper_address()) |
230 | |
231 | self.failUnlessFailure(d, UnitDeploymentError) |
232 | |
233 | @@ -165,7 +167,7 @@ |
234 | """ |
235 | error = self.assertRaises( |
236 | UnitDeploymentError, |
237 | - self.deployment.start, "0", "localhost:2181") |
238 | + self.deployment.start, "0", get_test_zookeeper_address()) |
239 | self.assertIn("Formula must be unpacked first.", str(error)) |
240 | |
241 | @inlineCallbacks |
242 | @@ -175,7 +177,7 @@ |
243 | on the machine, and kills the unit agent process. |
244 | """ |
245 | self.deployment.unpack_formula(self.bundle) |
246 | - yield self.deployment.start("0", "localhost:2181") |
247 | + yield self.deployment.start("0", get_test_zookeeper_address()) |
248 | # give the process time to write its pid file |
249 | yield self.sleep(0.1) |
250 | pid = int(open(self.deployment.pid_file).read()) |
251 | |
252 | === modified file 'ensemble/providers/dummy.py' |
253 | --- ensemble/providers/dummy.py 2010-09-30 00:31:07 +0000 |
254 | +++ ensemble/providers/dummy.py 2010-11-08 23:03:47 +0000 |
255 | @@ -5,10 +5,10 @@ |
256 | from txzookeeper import ZookeeperClient |
257 | from twisted.internet.defer import succeed, fail |
258 | |
259 | +import ensemble |
260 | from ensemble.errors import FileNotFound, ProviderError |
261 | from ensemble.machine import ProviderMachine |
262 | |
263 | - |
264 | class DummyMachine(ProviderMachine): |
265 | """Provider machine implementation specific to the dummy provider.""" |
266 | |
267 | @@ -23,7 +23,7 @@ |
268 | |
269 | def connect(self): |
270 | """Connect to the zookeeper ensemble running in the machine provider""" |
271 | - return ZookeeperClient("127.0.0.1:2181").connect() |
272 | + return ZookeeperClient(os.environ.get("ZOOKEEPER_ADDRESS", "127.0.0.1:2181")).connect() |
273 | |
274 | def list_machines(self): |
275 | """List all the machine running in the provider.""" |
276 | |
277 | === modified file 'ensemble/providers/ec2/tests/test_connect.py' |
278 | --- ensemble/providers/ec2/tests/test_connect.py 2010-10-14 21:14:05 +0000 |
279 | +++ ensemble/providers/ec2/tests/test_connect.py 2010-11-08 23:03:47 +0000 |
280 | @@ -20,6 +20,7 @@ |
281 | from ensemble.providers.ec2.tests.common import EC2TestMixin |
282 | from ensemble.providers.ec2.connect import EC2Connect |
283 | from ensemble.errors import ProviderInteractionError |
284 | +from ensemble.tests.common import get_test_zookeeper_address |
285 | |
286 | |
287 | class EC2ConnectTest(EC2TestMixin, TestCase): |
288 | @@ -154,7 +155,7 @@ |
289 | self.mocker.replay() |
290 | |
291 | zookeeper.set_debug_level(0) |
292 | - yield connected_client.connect("localhost:2181") |
293 | + yield connected_client.connect(get_test_zookeeper_address()) |
294 | |
295 | client_result = [] |
296 | |
297 | |
298 | === modified file 'ensemble/providers/ec2/tests/test_launch.py' |
299 | --- ensemble/providers/ec2/tests/test_launch.py 2010-10-15 21:30:31 +0000 |
300 | +++ ensemble/providers/ec2/tests/test_launch.py 2010-11-08 23:03:47 +0000 |
301 | @@ -13,6 +13,7 @@ |
302 | from ensemble.providers.common import ( |
303 | DEFAULT_REPOSITORIES, DEFAULT_PACKAGES, BOOTSTRAP_PACKAGES) |
304 | from ensemble.state.auth import make_identity |
305 | +from ensemble.tests.common import get_test_zookeeper_address |
306 | |
307 | from ensemble.lib.testing import TestCase |
308 | from ensemble.lib.mocker import MATCH |
309 | @@ -167,7 +168,7 @@ |
310 | launch = EC2LaunchMachine(provider) |
311 | variables = launch.get_machine_variables( |
312 | machine_data={"machine-id": "machine-2", |
313 | - "ensemble-zookeeper-hosts": "127.0.0.1:2181"}) |
314 | + "ensemble-zookeeper-hosts": get_test_zookeeper_address()}) |
315 | cmd = "cd /usr/lib/ensemble/ensemble && sudo /usr/bin/bzr switch %s"%( |
316 | custom_branch) |
317 | self.failUnlessIn(cmd, variables["scripts"]) |
318 | |
319 | === modified file 'ensemble/providers/tests/test_dummy.py' |
320 | --- ensemble/providers/tests/test_dummy.py 2010-09-30 00:31:07 +0000 |
321 | +++ ensemble/providers/tests/test_dummy.py 2010-11-08 23:03:47 +0000 |
322 | @@ -113,8 +113,7 @@ |
323 | |
324 | def setUp(self): |
325 | directory = self.makeDir() |
326 | - self.provider = MachineProvider( |
327 | - "foo", {"storage-directory": directory}) |
328 | + self.provider = MachineProvider("foo", {"storage-directory": directory}) |
329 | self.storage = self.provider.get_file_storage() |
330 | |
331 | def test_get_file_non_existant(self): |
332 | |
333 | === modified file 'ensemble/state/tests/common.py' |
334 | --- ensemble/state/tests/common.py 2010-10-21 12:12:26 +0000 |
335 | +++ ensemble/state/tests/common.py 2010-11-08 23:03:47 +0000 |
336 | @@ -10,6 +10,7 @@ |
337 | from ensemble.formula.tests.test_directory import sample_directory |
338 | from ensemble.formula.directory import FormulaDirectory |
339 | from ensemble.state.topology import InternalTopology |
340 | +from ensemble.tests.common import get_test_zookeeper_address |
341 | |
342 | |
343 | class StateTestBase(TestCase): |
344 | @@ -18,7 +19,7 @@ |
345 | def setUp(self): |
346 | zookeeper.set_debug_level(0) |
347 | self.formula = FormulaDirectory(sample_directory) |
348 | - self.client = ZookeeperClient("localhost:2181") |
349 | + self.client = ZookeeperClient(get_test_zookeeper_address()) |
350 | |
351 | yield self.client.connect() |
352 | yield self.client.create("/formulas") |
353 | @@ -32,7 +33,7 @@ |
354 | # Close and reopen connection, so that watches set during |
355 | # testing are not affected by the cleaning up. |
356 | self.client.close() |
357 | - client = ZookeeperClient("localhost:2181") |
358 | + client = ZookeeperClient(get_test_zookeeper_address()) |
359 | yield client.connect() |
360 | deleteTree(handle=client.handle) |
361 | client.close() |
362 | |
363 | === modified file 'ensemble/state/tests/test_initialize.py' |
364 | --- ensemble/state/tests/test_initialize.py 2010-09-22 18:59:04 +0000 |
365 | +++ ensemble/state/tests/test_initialize.py 2010-11-08 23:03:47 +0000 |
366 | @@ -9,6 +9,7 @@ |
367 | from ensemble.state.initialize import StateHierarchy |
368 | from ensemble.state.machine import MachineStateManager |
369 | from ensemble.lib.testing import TestCase |
370 | +from ensemble.tests.common import get_test_zookeeper_address |
371 | |
372 | |
373 | class LayoutTest(TestCase): |
374 | @@ -16,7 +17,7 @@ |
375 | def setUp(self): |
376 | self.log = self.capture_logging("ensemble.state.init") |
377 | zookeeper.set_debug_level(0) |
378 | - self.client = ZookeeperClient("localhost:2181") |
379 | + self.client = ZookeeperClient(get_test_zookeeper_address()) |
380 | self.identity = make_identity("admin:genie") |
381 | self.layout = StateHierarchy( |
382 | self.client, self.identity, "i-abcdef") |
383 | |
384 | === added file 'ensemble/tests/common.py' |
385 | --- ensemble/tests/common.py 1970-01-01 00:00:00 +0000 |
386 | +++ ensemble/tests/common.py 2010-11-08 23:03:47 +0000 |
387 | @@ -0,0 +1,152 @@ |
388 | +import logging |
389 | +import os |
390 | +import os.path |
391 | +import shutil |
392 | +import subprocess |
393 | +import sys |
394 | +import tempfile |
395 | + |
396 | +from contextlib import contextmanager |
397 | +from glob import glob |
398 | + |
399 | + |
400 | +__all__ = ("ManagedZooKeeper", |
401 | + "zookeeper_test_context", |
402 | + "get_zookeeper_test_address") |
403 | + |
404 | +log = logging.getLogger("ensemble.tests.common") |
405 | + |
406 | + |
407 | +class ManagedZooKeeper(object): |
408 | + """Class to manage the running of a ZooKeeper instance for testing. |
409 | + |
410 | + Note: no attempt is made to probe the ZooKeeper instance is |
411 | + actually available, or that the selected port is free. In the |
412 | + future, we may want to do that, especially when run in a |
413 | + Hudson/Buildbot context, to ensure more test robustness.""" |
414 | + |
415 | + def __init__(self, install_path, port=28181): |
416 | + """Define the ZooKeeper test instance. |
417 | + |
418 | + @param install_path: The path to the install for ZK |
419 | + @param port: The port to run the managed ZK instance |
420 | + """ |
421 | + |
422 | + self.install_path = install_path |
423 | + self.port = port |
424 | + self.host = "127.0.0.1" |
425 | + |
426 | + def run(self): |
427 | + """Run the ZooKeeper instance under a temporary directory. |
428 | + |
429 | + Writes ZK log messages to zookeeper.log in the current directory. |
430 | + """ |
431 | + |
432 | + self.working_path = tempfile.mkdtemp() |
433 | + log.info("Starting test zookeeper: installed=%s, port=%s", |
434 | + self.install_path, self.port) |
435 | + |
436 | + config_path = os.path.join(self.working_path, "zoo.cfg") |
437 | + log_path = os.path.join(self.working_path, "log") |
438 | + log4j_path = os.path.join(self.working_path, "log4j.properties") |
439 | + data_path = os.path.join(self.working_path, "data") |
440 | + |
441 | + # various setup steps |
442 | + os.mkdir(log_path) |
443 | + |
444 | + with open(config_path, "w") as config: |
445 | + config.write(""" |
446 | +tickTime=2000 |
447 | +dataDir=%s |
448 | +clientPort=%s |
449 | +""" % (data_path, self.port)) |
450 | + |
451 | + with open(log4j_path, "w") as log4j: |
452 | + log4j.write(""" |
453 | +# DEFAULT: console appender only |
454 | +log4j.rootLogger=INFO, ROLLINGFILE |
455 | +log4j.appender.ROLLINGFILE.layout=org.apache.log4j.PatternLayout |
456 | +log4j.appender.ROLLINGFILE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n |
457 | +log4j.appender.ROLLINGFILE=org.apache.log4j.RollingFileAppender |
458 | +log4j.appender.ROLLINGFILE.Threshold=DEBUG |
459 | +log4j.appender.ROLLINGFILE.File=zookeeper.log |
460 | +""") |
461 | + |
462 | + self.process = subprocess.Popen( |
463 | + args=["java", |
464 | + "-cp", self.classpath, |
465 | + "-Dzookeeper.log.dir=%s" % log_path, |
466 | + "-Dzookeeper.root.logger=INFO,CONSOLE", |
467 | + "-Dlog4j.configuration=file:%s" % log4j_path, |
468 | + # "-Dlog4j.debug", |
469 | + "org.apache.zookeeper.server.quorum.QuorumPeerMain", |
470 | + config_path], |
471 | + ) |
472 | + |
473 | + @property |
474 | + def classpath(self): |
475 | + """Get the classpath necessary to run ZooKeeper.""" |
476 | + |
477 | + # Two possibilities, as seen in zkEnv.sh: |
478 | + # Check for a release - top-level zookeeper-*.jar? |
479 | + jars = glob((os.path.join( |
480 | + self.install_path, 'zookeeper-*.jar'))) |
481 | + if jars: |
482 | + # Relase build (`ant package`) |
483 | + jars.extend(glob(os.path.join( |
484 | + self.install_path, |
485 | + "lib/*.jar"))) |
486 | + else: |
487 | + # Development build (plain `ant`) |
488 | + jars = glob((os.path.join( |
489 | + self.install_path, 'build/zookeeper-*.jar'))) |
490 | + jars.extend(glob(os.path.join( |
491 | + self.install_path, |
492 | + "build/lib/*.jar"))) |
493 | + return ":".join(jars) |
494 | + |
495 | + @property |
496 | + def address(self): |
497 | + """Get the address of the ZooKeeper instance.""" |
498 | + return "%s:%s" % (self.host, self.port) |
499 | + |
500 | + def stop(self): |
501 | + """Stop the ZooKeeper instance and cleanup.""" |
502 | + self.process.terminate() |
503 | + shutil.rmtree(self.working_path) |
504 | + |
505 | + |
506 | +"""Global to manage the ZK test address - only for testing of course!""" |
507 | +_zookeeper_address = "127.0.0.1:2181" |
508 | + |
509 | + |
510 | +def get_test_zookeeper_address(): |
511 | + """Get the current test ZK address, such as '127.0.0.1:2181'""" |
512 | + return _zookeeper_address |
513 | + |
514 | + |
515 | +@contextmanager |
516 | +def zookeeper_test_context(install_path, port=28181): |
517 | + """Manage context to run/stop a ZooKeeper for testing and related vars. |
518 | + |
519 | + @param install_path: The path to the install for ZK |
520 | + @param port: The port to run the managed ZK instance |
521 | + """ |
522 | + global _zookeeper_address |
523 | + |
524 | + saved_zookeeper_address = _zookeeper_address |
525 | + saved_env = os.environ.get("ZOOKEEPER_ADDRESS") |
526 | + test_zookeeper = ManagedZooKeeper(install_path, port) |
527 | + test_zookeeper.run() |
528 | + os.environ["ZOOKEEPER_ADDRESS"] = _zookeeper_address = test_zookeeper.address |
529 | + try: |
530 | + yield test_zookeeper |
531 | + finally: |
532 | + test_zookeeper.stop() |
533 | + _zookeeper_address = saved_zookeeper_address |
534 | + if saved_env: |
535 | + os.environ["ZOOKEEPER_ADDRESS"] = saved_env |
536 | + else: |
537 | + del os.environ["ZOOKEEPER_ADDRESS"] |
538 | + |
539 | + |
540 | |
541 | === modified file 'test' |
542 | --- test 2010-07-08 22:47:52 +0000 |
543 | +++ test 2010-11-08 23:03:47 +0000 |
544 | @@ -1,12 +1,17 @@ |
545 | #!/usr/bin/env python |
546 | +import os |
547 | import sys |
548 | -import os |
549 | from twisted.scripts.trial import run |
550 | |
551 | +from ensemble.tests.common import zookeeper_test_context |
552 | |
553 | FUNCTIONAL = '--functional' |
554 | |
555 | def main(args): |
556 | + if not os.environ["ZOOKEEPER_PATH"]: |
557 | + print "Environment variable ZOOKEEPER_PATH must be defined" |
558 | + exit() |
559 | + |
560 | matched = [arg for arg in args if arg.startswith("ensemble")] |
561 | |
562 | if FUNCTIONAL in sys.argv: |
563 | @@ -20,8 +25,11 @@ |
564 | packages.remove("ftests") |
565 | sys.argv.extend( |
566 | ["ensemble.%s"%p for p in packages]) |
567 | - run() |
568 | + |
569 | + with zookeeper_test_context( |
570 | + os.environ["ZOOKEEPER_PATH"], |
571 | + os.environ.get("ZOOKEEPER_TEST_PORT", 28181)): |
572 | + run() |
573 | |
574 | if __name__ == "__main__": |
575 | main(sys.argv[1:]) |
576 | - |
Hey Jim,
Here are some preliminary comments:
[1]
+"""This port can be overriden when testing""" LOCAL_ADDRESS = "127.0.0.1:2181"
+ZOOKEEPER_PORT = 2181
+
+"""For convenience, this should be kept synchronized with the ZOOKEEPER_PORT"""
+ZOOKEEPER_
We've managed to avoid global variables thus far, and it'd
be nice if we could manage to maintain things that way. It's
fine to have these defined for tests, though, but this is
sitting outside of the test environment.
Also, this is minor, since we're talking about tests, but this
redundancy feels a bit strange. You can just define this latter
one and very easily obtain the port number out of it. That said,
the port number isn't even used anywhere, so let's just get rid
of it entirely.
+# some standard dependency injection in the Java parlance we need to do
+# to override our normal assumptions so we can test
As a random comment, dependency injection is somewhat like
the opposite of a global variable. :-)
[2]
The branch is missing some pieces. zookeeper_ test_context wasn't added, specifically.