115
161
fake_url = u"http://example.com/project"
116
162
self.slave.startBuild(u"testing", fake_url)
163
self.assertEqual(["testing", fake_url],
164
self.slave.currentBuild.arguments)
117
165
self.assertEqual(
118
166
"Starting build: /usr/bin/offspring-build testing %s\n" % fake_url,
119
167
log_file.getvalue())
169
def test_current_build_is_active_no_current_build(self):
170
"""Test currentBuildIsActive returns False when currentBuild = False"""
172
# To start with, currentBuild should be None, so active == False
173
self.assertEqual(self.slave.currentBuild, None)
174
self.assertEqual(self.slave.currentBuildIsActive(), False)
176
def test_current_build_is_active_build_in_active_state(self):
177
"""Test currentBuildIsActive returns True when build is active"""
178
# Set up a fake build process (starts off in STATE_ACTIVE)
179
self.slave.currentBuild = FakeProjectBuildProcess()
181
# Now we have started a build, currentBuild has been assigned.
182
self.assertNotEqual(self.slave.currentBuild, None)
183
self.assertEqual(self.slave.currentBuild.state,
184
ProjectBuildProcess.STATE_ACTIVE)
185
self.assertEqual(self.slave.currentBuildIsActive(), True)
187
def test_current_build_is_active_build_state_not_active(self):
188
"""Test currentBuildIsActive when currentBuild state != STATE_ACTIVE
190
currentBuildIsActive should return False when
191
slave.currentBuild.state != STATE_ACTIVE. Clearly, currentBuild needs
192
to be valid for this test.
196
# Set up a fake build process (starts off in STATE_ACTIVE)
197
self.slave.currentBuild = FakeProjectBuildProcess()
199
self.slave.currentBuild.state = ProjectBuildProcess.STATE_SUCCESS
200
self.assertEqual(self.slave.currentBuildIsActive(), False)
202
self.slave.currentBuild.state = ProjectBuildProcess.STATE_FAILED
203
self.assertEqual(self.slave.currentBuildIsActive(), False)
205
def test_add_user_to_bzr_ssh_url(self):
206
"""Test lp:project URL form converted into bzr+ssh form adding user."""
208
config_url = u"lp:~dooferlad/offspring/config"
211
updated_url = self.slave.addUserToBzrSshUrl(config_url, user)
212
expected_url = ("bzr+ssh://user@bazaar.launchpad.net/~dooferlad/" +
214
self.assertEqual(updated_url, expected_url)
216
def test_start_build_with_custom_ssh_key(self):
217
"""Test slave converts lp:project URL into bzr+ssh form adding user.
219
When sending a URL to the slave in the form lp:project, and
220
a user and SSH key are sent as well, that the slave adds the user
221
into the URL so a private config branch can be checked out (URL needs
222
to be modified to send the user name through BZR to SSH).
224
log_file = self.capture_logging()
225
build_process_mock = self.mocker.patch(ProjectBuildProcess)
226
build_process_mock.start()
228
sshkey_filename = self.makeFile()
229
tempfile_mock = self.mocker.replace("tempfile.NamedTemporaryFile")
231
self.mocker.result(open(sshkey_filename, "w"))
235
config_url = u"lp:~dooferlad/offspring/config"
236
args = ["project_name", config_url, "user", "key"]
238
self.slave.startBuild(*args)
240
new_url = re.sub("lp:",
241
"bzr+ssh://" + args[2] + "@bazaar.launchpad.net/",
243
self.assertEqual(["project_name", new_url, sshkey_filename],
244
self.slave.currentBuild.arguments)
246
"Starting build: /usr/bin/offspring-build project_name %s\n"
247
% new_url, log_file.getvalue())
249
def test_check_ssh_key_deleted_when_build_finished(self):
250
"""Test SSH key file lifetime management.
252
The SSH key of a project is written to a temporary file when the
253
build process starts, this is tested by the first assert. When the
254
build has finished or is stopped, the key file is deleted. This is
255
tested by the second assert.
257
log_file = self.capture_logging()
258
build_process_mock = self.mocker.patch(ProjectBuildProcess)
259
build_process_mock.start()
262
config_url = u"lp:~dooferlad/offspring/config"
263
args = ["project_name", config_url, "user", "key"]
265
self.slave.startBuild(*args)
267
file_path = self.slave.currentBuild.ssh_key_file.name
268
read_key = open(file_path).read()
270
# We should have a key written to a file for the slave to read...
271
self.assertEqual(read_key, args[3])
273
# Can't use XMLRPC interface here because it checks to see if a build
274
# has actually started. We just want to test the clean up logic.
275
self.slave.stopBuild()
276
# Having stopped the slave, the key should have been erased
277
self.assertFalse(os.path.exists(file_path))
279
def test_check_ssh_agent(self):
280
"""Check that SSH agent is started and killed by build.
282
In order to pass private keys to SSH, the build script starts
283
ssh-agent if a private key file is passed to it. When the build has
284
completed, the build script kills SSH agent.
286
This test uses the called shell script to test the functions used
287
by the build script to start ssh-agent, load the specified key and
288
kill the started ssh-agent process.
291
# This is a wrapper around a shell script that runs the commands and
292
# we just check the output.
294
dir_of_this_file = os.path.dirname(os.path.abspath(__file__))
295
test_script = os.path.join(dir_of_this_file,
296
"../../build/tests/test.sh")
298
test_script_process = subprocess.Popen(test_script,
299
stdout=subprocess.PIPE,
300
stderr=subprocess.STDOUT)
302
stdout = test_script_process.communicate()[0]
304
lines = stdout.split("\n")
308
# Identity added: /bla/offspring_test_key (/bla/offspring_test_key)
309
# After loading test key, keys are:
310
# 2048 <fingerprint> /bla/offspring_test_key (RSA)
311
# After killing ssh agent, we shouldn't be able to find any keys...
312
# Could not open a connection to your authentication agent.
314
# Expect the first line to be of the form "Agent pid [PID]"
315
self.assertTrue(re.search("Agent pid \d+", lines[0]),
316
"Test build ssh-agent check failed: Expected to find" +
317
"\nAgent pid <PID>\nfound:\n" + lines[0])
319
# Expect the second line to be of the form:
320
# Identity added: <path to...>/offspring_test_key
321
self.assertTrue(re.search("Identity added:.*?offspring_test_key",
323
"Test build ssh-agent check failed: Expected to find" +
324
"\nIdentity added: <path to...>/offspring_test_key" +
325
"\nfound:\n" + lines[1])
327
# Ignore the third line, it is just a fixed message
328
# Expect the fourth line to be:
329
# <key fingerprint> /path/to/offspring_test_key (RSA)
330
self.assertTrue(re.search("offspring_test_key \(RSA\)", lines[3]),
331
"Test build ssh-agent check failed: Expected to find" +
332
"\noffspring_test_key (RSA)\nfound:\n" + lines[3])
334
# Ignore the fifth line, it is just a fixed message
335
# Expect the sixth line to be:
336
# "Could not open a connection to your authentication agent."
338
"^Could not open a connection to your authentication agent\.$")
339
message = ("Test build ssh-agent check failed: Expected to find" +
340
"\nCould not open a connection to your authentication" +
341
"agent\n, found:\n" + lines[5])
342
self.assertTrue(re.search(search_string, lines[5]), message)
345
class FakeLexbuilderSlave(object):
346
"""Fake slave for use in tests."""
347
def __init__(self, state=LexbuilderSlave.STATE_IDLE):
348
self.project_name = None
349
self.config_url = None
353
def startBuild(self, project_name, config_url):
354
self.project_name = project_name
355
self.config_url = config_url
361
def currentBuildIsActive(self):
362
"""Return True if current build is active, else False."""
363
if self.currentBuild is not None:
364
return self.currentBuild.state == ProjectBuildProcess.STATE_ACTIVE
368
class TestLexbuilderSlaveServer(LexbuilderSlaveTestMixin,
369
CaptureLoggingTestCase):
371
Tests for the LexbuilderSlaveServer class.
375
super(TestLexbuilderSlaveServer, self).setUp()
376
self.fake_slave = FakeLexbuilderSlave()
378
def create_slave_server(self):
380
Instantiate a slave server for the fake_slave.
382
return LexbuilderSlaveServer(
383
self.fake_slave, ("localhost", 8760), self.config)
385
def test_create(self):
387
We can instantiate a new LexbuilderSlaveServer.
389
log_file = self.capture_logging()
390
slave_server = self.create_slave_server()
391
self.assertEqual(self.fake_slave, slave_server.slave)
392
self.assertEqual(("127.0.0.1", 8760), slave_server.server_address)
393
self.assertEqual(self.config, slave_server.config)
394
self.assertEqual("", log_file.getvalue())
396
def test_api_start_build(self):
398
If the server doesn't have an active build, then api_startBuild
399
should start a build on the slave with the correct project name
400
and configuration URL.
402
self.fake_slave.currentBuild = FakeBuild()
403
slave_server = self.create_slave_server()
405
response = slave_server.api_startBuild("testing",
406
"http://example.com/")
407
self.assertEqual(LexbuilderSlaveServer.REQUEST_OKAY, response)
408
self.assertEqual("testing", self.fake_slave.project_name)
409
self.assertEqual("http://example.com/", self.fake_slave.config_url)
410
self.assertTrue(self.fake_slave.started)
412
def test_api_start_build_with_busy_server(self):
414
If the server has an active build in progress, api_startBuild should
415
return an error state.
417
slave_server = self.create_slave_server()
419
self.fake_slave.currentBuild = FakeBuild(
420
state=ProjectBuildProcess.STATE_ACTIVE)
422
response = slave_server.api_startBuild("testing",
423
"http://example.com/")
424
self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR, response)
425
self.assertFalse(self.fake_slave.project_name)
426
self.assertFalse(self.fake_slave.config_url)
428
def test_api_stop_build(self):
430
If the SlaveServer has an active build, api_stopBuild should end the
433
slave_server = self.create_slave_server()
434
self.fake_slave.currentBuild = FakeBuild(
435
state=ProjectBuildProcess.STATE_ACTIVE)
437
response = slave_server.api_stopBuild()
438
self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR, response)
439
self.assertFalse(self.fake_slave.stopped)
441
def test_api_stop_build_with_correct_platform_version(self):
443
If platform.version is > 2.6, and we have a current build, then we
444
can stop the build slave.
446
FIXME: Add documentation for why?
448
version_mock = self.mocker.replace("platform.version")
450
self.mocker.result("2.7")
452
slave_server = self.create_slave_server()
453
self.fake_slave.currentBuild = FakeBuild(
454
state=ProjectBuildProcess.STATE_ACTIVE)
456
response = slave_server.api_stopBuild()
457
self.assertEqual(LexbuilderSlaveServer.REQUEST_OKAY, response)
458
self.assertTrue(self.fake_slave.stopped)
460
def test_api_stop_build_no_current_build(self):
462
If the SlaveServer does not have an active build, api_stopBuild should
463
return an error state.
465
slave_server = self.create_slave_server()
466
self.fake_slave.currentBuild = FakeBuild()
468
response = slave_server.api_stopBuild()
469
self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR, response)
470
self.assertFalse(self.fake_slave.stopped)
472
def test_api_get_builder_name(self):
474
LexbuilderSlaveServer.api_getBuilderName should return the network name
477
slave_server = self.create_slave_server()
478
self.assertEqual(platform.node(), slave_server.api_getBuilderName())
480
def test_api_get_machine_type(self):
482
LexbuilderSlaveServer.api_getMachineType should return the platform the
483
builder is running on.
485
slave_server = self.create_slave_server()
486
self.assertEqual(platform.machine(), slave_server.api_getMachineType())
488
def test_api_get_builder_state(self):
490
LexbuilderSlaveServer.api_getBuilderState should return the current
491
state of the underlying LexBuilderSlave.
493
slave_server = self.create_slave_server()
494
self.assertEqual(LexbuilderSlave.STATE_IDLE,
495
slave_server.api_getBuilderState())
497
def test_api_get_build_project_name(self):
499
LexbuilderSlaveServer.api_getBuildProjectName should return the name of
500
the current build project.
502
slave_server = self.create_slave_server()
503
self.fake_slave.currentBuild = FakeBuild(project_name=u"testproject")
504
self.assertEqual(u"testproject",
505
slave_server.api_getBuildProjectName())
507
def test_api_get_build_project_name_with_no_active_build(self):
509
LexbuilderSlaveServer.api_getBuildProjectName should return an error
510
state if there's no current build project.
512
slave_server = self.create_slave_server()
513
self.fake_slave.currentBuild = None
514
self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR,
515
slave_server.api_getBuildProjectName())
517
def test_api_get_build_name(self):
519
LexbuilderSlaveServer.getBuildName should return the name of the
522
slave_server = self.create_slave_server()
523
self.fake_slave.currentBuild = FakeBuild(name=u"testbuild")
524
self.assertEqual(u"testbuild",
525
slave_server.api_getBuildName())
527
def test_api_get_build_name_with_no_active_build(self):
529
LexbuilderSlaveServer.api_getBuildName should return an error
530
state if there's no current build project.
532
slave_server = self.create_slave_server()
533
self.fake_slave.currentBuild = None
534
self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR,
535
slave_server.api_getBuildName())
537
def test_api_get_build_result(self):
539
LexbuilderSlaveServer.getBuildResult should return the result of the
542
slave_server = self.create_slave_server()
543
self.fake_slave.currentBuild = FakeBuild()
544
self.assertEqual(ProjectBuildProcess.STATE_SUCCESS,
545
slave_server.api_getBuildResult())
547
def test_api_get_build_result_with_no_active_build(self):
549
LexbuilderSlaveServer.api_getBuildResult should return an error
550
state if there's no current build.
552
slave_server = self.create_slave_server()
553
self.fake_slave.currentBuild = None
554
self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR,
555
slave_server.api_getBuildResult())
557
def test_api_get_build_started_at(self):
559
LexbuilderSlaveServer.getBuildStartedAt should return the time the
560
current build (if any) started at, using the xmlrpc DateTime
563
slave_server = self.create_slave_server()
564
current_build = FakeBuild(started_at=datetime.today())
565
self.fake_slave.currentBuild = current_build
566
self.assertEqual(xmlrpclib.DateTime(current_build.startedAt),
567
slave_server.api_getBuildStartedAt())
569
def test_api_get_build_started_at_with_no_active_build(self):
571
LexbuilderSlaveServer.api_getBuildStartedAt should return an error
572
state if there's no current build.
574
slave_server = self.create_slave_server()
575
self.fake_slave.currentBuild = None
576
self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR,
577
slave_server.api_getBuildStartedAt())
579
def test_api_get_build_started_at_with_no_start_time(self):
581
LexbuilderSlaveServer.api_getBuildStartedAt should return an error if
582
the start time of the current build is unknown.
584
slave_server = self.create_slave_server()
585
self.fake_slave.currentBuild = FakeBuild()
586
self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR,
587
slave_server.api_getBuildStartedAt())
589
def test_api_get_build_finished_at(self):
591
LexbuilderSlaveServer.getBuildFinishedAt should return the time the
592
current build (if any) finished at, using the xmlrpc DateTime
595
slave_server = self.create_slave_server()
596
current_build = FakeBuild(finished_at=datetime.today())
597
self.fake_slave.currentBuild = current_build
598
self.assertEqual(xmlrpclib.DateTime(current_build.finishedAt),
599
slave_server.api_getBuildFinishedAt())
601
def test_api_get_build_finished_at_with_no_active_build(self):
603
LexbuilderSlaveServer.api_getBuildFinishedAt should return an error
604
state if there's no current build.
606
slave_server = self.create_slave_server()
607
self.fake_slave.currentBuild = None
608
self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR,
609
slave_server.api_getBuildFinishedAt())
611
def test_api_get_build_finished_at_with_no_finish_time(self):
613
LexbuilderSlaveServer.api_getBuildFinishedAt should return an error if
614
the finish time of the current build is unknown.
616
slave_server = self.create_slave_server()
617
self.fake_slave.currentBuild = FakeBuild()
618
self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR,
619
slave_server.api_getBuildFinishedAt())
621
def test_api_get_build_error_code(self):
623
LexbuilderSlaveServer.getBuildErrorCode should return the current error
626
slave_server = self.create_slave_server()
627
current_build = FakeBuild(error_code=1)
628
self.fake_slave.currentBuild = current_build
629
self.assertEqual(1, slave_server.api_getBuildErrorCode())
631
def test_api_get_build_error_code_with_no_active_build(self):
633
LexbuilderSlaveServer.api_getBuildErrorCode should return an error
634
state if there's no current build.
636
slave_server = self.create_slave_server()
637
self.fake_slave.currentBuild = None
638
self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR,
639
slave_server.api_getBuildErrorCode())
641
def test_api_get_build_error_code_with_no_finish_time(self):
643
LexbuilderSlaveServer.api_getBuildErrorCode should return an error if
644
the error code of the current build is unknown.
646
slave_server = self.create_slave_server()
647
self.fake_slave.currentBuild = FakeBuild()
648
self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR,
649
slave_server.api_getBuildErrorCode())
651
def test_api_shutdown_slave_not_idle(self):
653
LexbuilderSlaveServer.api_shutdown should return an error if the slave
656
slave_server = self.create_slave_server()
657
self.fake_slave.currentBuild = FakeBuild()
658
self.fake_slave.state = ProjectBuildProcess.STATE_ACTIVE
659
self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR,
660
slave_server.api_shutdown())
662
def test_api_shutdown_slave_idle(self):
664
LexbuilderSlaveServer.api_shutdown should start a shutdown thread if
665
the slave is currently idle.
667
class FakeShutdownThread(object):
670
slave_server = self.create_slave_server()
671
self.fake_slave.currentBuild = FakeBuild()
673
mock_shutdown_thread = self.mocker.replace(ShutdownThread,
675
mock_shutdown_thread(slave_server)
676
fake_shutdown = FakeShutdownThread()
677
self.mocker.result(fake_shutdown)
680
self.assertEqual(LexbuilderSlaveServer.REQUEST_OKAY,
681
slave_server.api_shutdown())
682
self.assertTrue(fake_shutdown.started)