Merge lp:~bigkevmcd/offspring/slave-testing-integration into lp:offspring

Proposed by Kevin McDermott
Status: Merged
Merged at revision: 108
Proposed branch: lp:~bigkevmcd/offspring/slave-testing-integration
Merge into: lp:offspring
Prerequisite: lp:~bigkevmcd/offspring/slave-testing-server
Diff against target: 295 lines (+225/-8)
1 file modified
lib/offspring/slave/tests/test_slave.py (+225/-8)
To merge this branch: bzr merge lp:~bigkevmcd/offspring/slave-testing-integration
Reviewer Review Type Date Requested Status
Cody A.W. Somerville Approve
Review via email: mp+84959@code.launchpad.net

This proposal supersedes a proposal from 2011-12-07.

Description of the change

More tests...now hit 100% coverage for the slave classes

Still to do, TaskMonitor and integration, I want to add at least one test that kicks off a script that "builds" successfully.

To post a comment you must log in.
139. By Kevin McDermott

Merge trunk and fix conflicts.

Revision history for this message
Cody A.W. Somerville (cody-somerville) wrote :
Download full text (3.6 KiB)

I'm going to provide a review of the changes you make specifically in this merge proposal. I have been reviewing the other test code you've written and already merged and I do have some questions but I'll contact you regarding them OOB to this MP.

>>> === modified file 'lib/offspring/slave/tests/test_slave.py'
>>> --- lib/offspring/slave/tests/test_slave.py 2011-12-08 17:29:41 +0000
>>> +++ lib/offspring/slave/tests/test_slave.py 2011-12-08 17:48:24 +0000
<snip>
>>> @@ -191,12 +193,41 @@
>>> """
>>> We can instantiate a new LexbuilderSlaveServer.
>>> """
>>> - log_file = self.capture_logging()
>>> + log_file = self.capture_logging(log_level=logging.DEBUG)
>>> +
>>> slave_server = self.create_slave_server()
>>> self.assertEqual(self.fake_slave, slave_server.slave)
>>> self.assertEqual(("127.0.0.1", 8760), slave_server.server_address)
>>> self.assertEqual(self.config, slave_server.config)
>>> - self.assertEqual("", log_file.getvalue())
>>> + self.assertEqual("Request timeout set to 60.0 seconds\n",
>>> + log_file.getvalue())
>>> +

To avoid causing unnecessary breakage if the value for request_timeout changes in LexbuilderSlaveTestMixin, we should probably construct the string we compare against the log file using the value we configured.

>>> + def test_create_with_missing_request_timeout(self):
>>> + """
>>> + LexbuilderSlaveServer should set the request timeout to the value
>>> + specified in the configuration.
>>> + """
>>> + log_file = self.capture_logging(log_level=logging.DEBUG)
>>> + self.config.set("slave", "request_timeout", "30.0")
>>> +
>>> + slave_server = LexbuilderSlaveServer(
>>> + self.fake_slave, ("localhost", 8760), self.config)
>>> + self.assertEqual("Request timeout set to 30.0 seconds\n",
>>> + log_file.getvalue())
>>> +

Is the name of this test method accurate? It doesn't appear to be.

Also, what is the value of this test since we call 'self.config.set("slave", "request_timeout", "60")' in LexbuilderSlaveTestMixin.setUp?

>>> + def test_create_with_invalid_configuration(self):
>>> + """
>>> + LexbuilderSlaveServer should handle invalid request timeout values by
>>> + falling back to the default.
>>> + """
>>> + log_file = self.capture_logging(log_level=logging.DEBUG)
>>> + self.config.set("slave", "request_timeout", "broken value")
>>> +
>>> +
>>> + slave_server = LexbuilderSlaveServer(
>>> + self.fake_slave, ("localhost", 8760), self.config)
>>> + self.assertEqual("Request timeout set to 60.0 seconds\n",
>>> + log_file.getvalue())
>>>

As with test_create, we should probably not assume the default value / configured value. In this case, you'd have to inspect the __init__ method on LexbuilderSlaveServer or update it to use a constant that you can import.

Additionally, we don't actually get a connection and test that calling gettimeout on the socket returns the value we expect - I imagine this is something we should do to...

Read more...

review: Approve
Revision history for this message
Kevin McDermott (bigkevmcd) wrote :

> >>> + self.assertEqual("Request timeout set to 60.0 seconds\n",
> >>> + log_file.getvalue())
> >>> +
>
> To avoid causing unnecessary breakage if the value for request_timeout changes
> in LexbuilderSlaveTestMixin, we should probably construct the string we
> compare against the log file using the value we configured.

Given that we're testing the code, and if we change the code, we should have changed the tests to match, this is expected behaviour we're testing for...if you want to change the coded default value, change the test first...

> Is the name of this test method accurate? It doesn't appear to be.
>
> Also, what is the value of this test since we call 'self.config.set("slave",
> "request_timeout", "60")' in LexbuilderSlaveTestMixin.setUp?

Ooops...dunno what I was thinking with that test, renamed the test, and added an additional test for when it's actually missing.

> >>>
>
> As with test_create, we should probably not assume the default value /
> configured value. In this case, you'd have to inspect the __init__ method on
> LexbuilderSlaveServer or update it to use a constant that you can import.

See above...

> Additionally, we don't actually get a connection and test that calling
> gettimeout on the socket returns the value we expect - I imagine this is
> something we should do to properly test this.

The Slave doesn't actually do this, it's the responsibility of LexbuilderXMLRPCServer to set the timeout (which needs tests written to do this).

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/offspring/slave/tests/test_slave.py'
2--- lib/offspring/slave/tests/test_slave.py 2011-12-08 17:29:41 +0000
3+++ lib/offspring/slave/tests/test_slave.py 2011-12-08 17:48:24 +0000
4@@ -1,12 +1,16 @@
5 # Copyright 2011 Canonical Ltd. This software is licensed under the
6 # GNU Affero General Public License version 3 (see the file LICENSE).
7 import platform
8+import logging
9 from datetime import datetime
10 from ConfigParser import ConfigParser
11 import xmlrpclib
12
13+from mocker import MockerTestCase
14+
15 from offspring.tests.helpers import CaptureLoggingTestCase
16
17+from offspring.enums import ProjectBuildStates
18 from offspring.slave import (
19 LexbuilderSlave, ProjectBuildProcess, LexbuilderSlaveServer,
20 ShutdownThread)
21@@ -89,16 +93,13 @@
22 self.slave = LexbuilderSlave(("localhost", 8760), self.config,
23 self.fake_server)
24
25- def tearDown(self):
26- super(TestLexbuilderSlave, self).tearDown()
27-
28 def test_slave_run_starts_server_and_logs(self):
29 """
30 LexbuilderSlave.run should start the LexbuilderSlaveServer.
31 """
32 log_file = self.capture_logging()
33 self.slave.run()
34- self.assertIn("Slave ready to accept builds", log_file.getvalue())
35+ self.assertEqual("Slave ready to accept builds\n", log_file.getvalue())
36 self.assertTrue(self.fake_server.started)
37
38 def test_slave_stop(self):
39@@ -155,11 +156,12 @@
40
41 class FakeLexbuilderSlave(object):
42 """Fake slave for use in tests."""
43- def __init__(self, state=LexbuilderSlave.STATE_IDLE):
44+ def __init__(self, config=None, state=LexbuilderSlave.STATE_IDLE):
45 self.project_name = None
46 self.config_url = None
47 self.stopped = False
48 self.state = state
49+ self.config = config
50
51 def startBuild(self, project_name, config_url):
52 self.project_name = project_name
53@@ -178,7 +180,7 @@
54
55 def setUp(self):
56 super(TestLexbuilderSlaveServer, self).setUp()
57- self.fake_slave = FakeLexbuilderSlave()
58+ self.fake_slave = FakeLexbuilderSlave(self.config)
59
60 def create_slave_server(self):
61 """
62@@ -191,12 +193,41 @@
63 """
64 We can instantiate a new LexbuilderSlaveServer.
65 """
66- log_file = self.capture_logging()
67+ log_file = self.capture_logging(log_level=logging.DEBUG)
68+
69 slave_server = self.create_slave_server()
70 self.assertEqual(self.fake_slave, slave_server.slave)
71 self.assertEqual(("127.0.0.1", 8760), slave_server.server_address)
72 self.assertEqual(self.config, slave_server.config)
73- self.assertEqual("", log_file.getvalue())
74+ self.assertEqual("Request timeout set to 60.0 seconds\n",
75+ log_file.getvalue())
76+
77+ def test_create_with_missing_request_timeout(self):
78+ """
79+ LexbuilderSlaveServer should set the request timeout to the value
80+ specified in the configuration.
81+ """
82+ log_file = self.capture_logging(log_level=logging.DEBUG)
83+ self.config.set("slave", "request_timeout", "30.0")
84+
85+ slave_server = LexbuilderSlaveServer(
86+ self.fake_slave, ("localhost", 8760), self.config)
87+ self.assertEqual("Request timeout set to 30.0 seconds\n",
88+ log_file.getvalue())
89+
90+ def test_create_with_invalid_configuration(self):
91+ """
92+ LexbuilderSlaveServer should handle invalid request timeout values by
93+ falling back to the default.
94+ """
95+ log_file = self.capture_logging(log_level=logging.DEBUG)
96+ self.config.set("slave", "request_timeout", "broken value")
97+
98+
99+ slave_server = LexbuilderSlaveServer(
100+ self.fake_slave, ("localhost", 8760), self.config)
101+ self.assertEqual("Request timeout set to 60.0 seconds\n",
102+ log_file.getvalue())
103
104 def test_api_start_build(self):
105 """
106@@ -485,3 +516,189 @@
107 self.assertEqual(LexbuilderSlaveServer.REQUEST_OKAY,
108 slave_server.api_shutdown())
109 self.assertTrue(fake_shutdown.started)
110+
111+
112+class TestProjectBuildProcess(LexbuilderSlaveTestMixin, CaptureLoggingTestCase):
113+
114+ def setUp(self):
115+ super(TestProjectBuildProcess, self).setUp()
116+ self.master = FakeLexbuilderSlave(config=self.config)
117+ self.build_process = ProjectBuildProcess(
118+ self.master, u"testing", "http://example.com/project")
119+
120+ def test_create(self):
121+ """
122+ We can instantiate a new ProjectBuildProcess.
123+ """
124+ self.assertEqual(u"testing", self.build_process.projectName)
125+ self.assertEqual([u"testing", "http://example.com/project"],
126+ self.build_process.arguments)
127+
128+ def test_get_state_currently_active(self):
129+ """
130+ If ProjectBuildProcess.state is STATE_ACTIVE then
131+ ProjectBuildProcess.get_state() should return
132+ ProjectBuildStates.PENDING.
133+ """
134+ self.build_process.state = self.build_process.STATE_ACTIVE
135+ self.assertEqual(ProjectBuildStates.PENDING,
136+ self.build_process.getState())
137+
138+ def test_get_state_currently_failed(self):
139+ """
140+ If ProjectBuildProcess.state is STATE_FAILED then
141+ ProjectBuildProcess.get_state() should return
142+ ProjectBuildStates.FAILED.
143+ """
144+ self.build_process.state = self.build_process.STATE_FAILED
145+ self.assertEqual(ProjectBuildStates.FAILED,
146+ self.build_process.getState())
147+
148+ def test_get_state_currently_success(self):
149+ """
150+ If ProjectBuildProcess.state is STATE_SUCCESS then
151+ ProjectBuildProcess.get_state() should return
152+ ProjectBuildStates.SUCCESS.
153+ """
154+ self.build_process.state = self.build_process.STATE_SUCCESS
155+ self.assertEqual(ProjectBuildStates.SUCCESS,
156+ self.build_process.getState())
157+
158+ def test_get_state_currently_unknown_state(self):
159+ """
160+ If ProjectBuildProcess.state is not one of STATE_ACTIVE, STATE_FAILED
161+ or STATE_SUCCESS ProjectBuildProcess.get_state() should return
162+ ProjectBuildStates.UNKNOWN.
163+ """
164+ self.build_process.state = None
165+ self.assertEqual(ProjectBuildStates.UNKNOWN,
166+ self.build_process.getState())
167+
168+ def test_pre_execution(self):
169+ """
170+ ProjectBuildProcess.preExecution should log the fact that the slave is
171+ starting a build, and set the current state of the LexbuilderSlave to
172+ STATE_BUILDING.
173+ """
174+ log_file = self.capture_logging(log_level=logging.DEBUG)
175+
176+ self.build_process.preExecution()
177+ self.assertEqual(LexbuilderSlave.STATE_BUILDING,
178+ self.build_process.master.state)
179+ self.assertEqual("Setting state of slave to BUILDING\n",
180+ log_file.getvalue())
181+
182+ def test_post_execution(self):
183+ """
184+ ProjectBuildProcess.preExecution should log the fact that the slave is
185+ finishing a build, and set the current state of the LexbuilderSlave to
186+ STATE_IDLE.
187+ """
188+ log_file = self.capture_logging(log_level=logging.DEBUG)
189+
190+ self.build_process.postExecution()
191+ self.assertEqual(LexbuilderSlave.STATE_IDLE,
192+ self.build_process.master.state)
193+ self.assertEqual("Setting state of slave to IDLE\n",
194+ log_file.getvalue())
195+
196+ def test_get_command_path(self):
197+ """
198+ ProjectBuildProcess.getCommandPath should return the configured
199+ build_command.
200+ """
201+ self.config.set("slave", "build_command",
202+ "/usr/bin/offspring-build")
203+ self.assertEqual("/usr/bin/offspring-build",
204+ self.build_process.getCommandPath())
205+
206+ def test_get_project_name(self):
207+ """
208+ ProjectBuildProcess.getProjectName should return the name of the
209+ project currently configured for building.
210+ """
211+ self.build_process.projectName = u"testing"
212+ self.assertEqual(u"testing",
213+ self.build_process.getProjectName())
214+
215+ def test_get_build_name_with_known_build_name(self):
216+ """
217+ ProjectBuildProcess.getBuildName should return the name of the current
218+ build if known.
219+ """
220+ self.build_process.buildName = u"testing"
221+ self.assertEqual(u"testing", self.build_process.getBuildName())
222+
223+ def test_get_build_name_with_unknown_build_inactive_process(self):
224+ """
225+ If the build name is unknown, and the process is not currently active,
226+ then we ProjectBuildProcess.getBuildName should examine the output of
227+ the build process and look for START <projectname> <buildname> and
228+ extract the buildname and return it.
229+ """
230+ self.build_process.stdoutdata = "START project testproject mybuild\n"
231+ self.assertEqual("mybuild", self.build_process.getBuildName())
232+
233+ def test_get_build_name_with_unknown_build_active_process(self):
234+ """
235+ If the build name is unknown, and the process is currently active,
236+ then we ProjectBuildProcess.getBuildName should examine the the build
237+ statefile from the configuration and parse the project name and build
238+ name from there.
239+ """
240+ self.build_process.state = self.build_process.STATE_ACTIVE
241+ self.build_process.projectName = "newproject"
242+ build_statefile_name = self.makeFile("newproject newbuild\n")
243+ self.config.set("slave", "build_statefile", build_statefile_name)
244+ self.assertEqual("newbuild", self.build_process.getBuildName())
245+
246+ def test_get_build_name_with_unknown_build_active_invalid_project(self):
247+ """
248+ If the build name is unknown, and the process is currently active,
249+ then we ProjectBuildProcess.getBuildName should examine the the build
250+ statefile from the configuration and parse the project name and build
251+ name from there.
252+
253+ If the project name parsed from the build file doesn't match the
254+ project we're building, then we should get an error.
255+ """
256+ self.build_process.state = self.build_process.STATE_ACTIVE
257+ self.build_process.projectName = "anotherprojectentirely"
258+ build_statefile_name = self.makeFile("newproject newbuild\n")
259+ self.config.set("slave", "build_statefile", build_statefile_name)
260+
261+ self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR,
262+ self.build_process.getBuildName())
263+
264+ def test_get_build_name_with_unknown_build_active_process_error(self):
265+ """
266+ If the build name is unknown, and the process is currently active,
267+ then we ProjectBuildProcess.getBuildName should examine the the build
268+ statefile from the configuration, any error reading this should result
269+ in an error return.
270+ """
271+ self.build_process.state = self.build_process.STATE_ACTIVE
272+ self.build_process.projectName = "newproject"
273+ build_statefile_name = self.makeFile()
274+ self.config.set("slave", "build_statefile", build_statefile_name)
275+ self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR,
276+ self.build_process.getBuildName())
277+
278+
279+class TestShutdownThread(MockerTestCase):
280+ """
281+ Tests for the ShutdownThread class.
282+ """
283+
284+ def test_run(self):
285+ """
286+ """
287+ sleep_mock = self.mocker.replace("time.sleep")
288+ sleep_mock(1)
289+ self.mocker.replay()
290+ master = FakeLexbuilderSlaveServer()
291+
292+ shutdown_thread = ShutdownThread(master)
293+ shutdown_thread.run()
294+
295+ self.assertTrue(master.stopped)

Subscribers

People subscribed via source and target branches