~salgado/offspring/builder_details

« back to all changes in this revision

Viewing changes to lib/offspring/slave/tests/test_slave.py

  • Committer: Guilherme Salgado
  • Date: 2011-12-15 14:03:57 UTC
  • mfrom: (106.1.2 list-views)
  • Revision ID: salgado@canonical.com-20111215140357-g7zmfn1ie5g2xjit
merge list-views branch

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright 2011 Canonical Ltd.  This software is licensed under the
2
2
# GNU Affero General Public License version 3 (see the file LICENSE).
 
3
 
3
4
from ConfigParser import ConfigParser
 
5
from datetime import datetime
 
6
import os
 
7
import platform
 
8
import re
 
9
import subprocess
 
10
import xmlrpclib
4
11
 
5
12
from offspring.tests.helpers import CaptureLoggingTestCase
6
13
 
7
 
from offspring.slave import LexbuilderSlave, ProjectBuildProcess
 
14
from offspring.slave import (
 
15
    LexbuilderSlave, ProjectBuildProcess, LexbuilderSlaveServer,
 
16
    ShutdownThread)
8
17
 
9
18
 
10
19
class FakeLexbuilderSlaveServer(object):
31
40
        def kill(self):
32
41
            self.killed = True
33
42
 
34
 
    def __init__(self):
 
43
    def __init__(self, project_name=None, name=None, started_at=None,
 
44
                 finished_at=None, error_code=None,
 
45
                 state=ProjectBuildProcess.STATE_SUCCESS):
35
46
        self.process = self.FakeBuildProcess()
36
 
 
37
 
 
38
 
class TestLexbuilderSlave(CaptureLoggingTestCase):
 
47
        self.state = state
 
48
        self.startedAt = started_at
 
49
        self.finishedAt = finished_at
 
50
        self.project_name = project_name
 
51
        self.name = name
 
52
        self.errorCode = error_code
 
53
 
 
54
    def getState(self):
 
55
        return self.state
 
56
 
 
57
    def getProjectName(self):
 
58
        return self.project_name
 
59
 
 
60
    def getBuildName(self):
 
61
        return self.name
 
62
 
 
63
    def tidyUp(self):
 
64
        self.tidied = True
 
65
 
 
66
 
 
67
class LexbuilderSlaveTestMixin(object):
 
68
    """
 
69
    Sets up a configuration object usable by Slave tests.
 
70
    """
39
71
 
40
72
    def setUp(self):
41
 
        super(TestLexbuilderSlave, self).setUp()
 
73
        super(LexbuilderSlaveTestMixin, self).setUp()
42
74
        self.log_dir = self.makeDir()
43
75
        self.config = ConfigParser()
44
76
        self.config.add_section("slave")
51
83
        self.config.set("slave", "request_timeout", "60")
52
84
        self.config.set("slave", "logfile", "%(log_dir)s/offspring-slave-log")
53
85
        self.config.set("slave", "port", 8765)
 
86
 
 
87
 
 
88
class FakeProjectBuildProcess(object):
 
89
    state = ProjectBuildProcess.STATE_ACTIVE
 
90
 
 
91
 
 
92
class TestLexbuilderSlave(LexbuilderSlaveTestMixin, CaptureLoggingTestCase):
 
93
    """
 
94
    Tests for the LexbuilderSlave class.
 
95
    """
 
96
 
 
97
    def setUp(self):
 
98
        super(TestLexbuilderSlave, self).setUp()
54
99
        self.fake_server = FakeLexbuilderSlaveServer()
55
 
        self.slave = LexbuilderSlave(('localhost', 8760), self.config,
 
100
        self.slave = LexbuilderSlave(("localhost", 8760), self.config,
56
101
                                     self.fake_server)
57
102
 
58
103
    def tearDown(self):
101
146
 
102
147
        self.assertTrue(fake_build.process.terminated)
103
148
        self.assertTrue(fake_build.process.killed)
 
149
        self.assertTrue(fake_build.tidied)
104
150
        self.assertEqual("Killing active build.\n", log_file.getvalue())
105
151
 
106
152
    def test_slave_start_build(self):
114
160
 
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())
 
168
 
 
169
    def test_current_build_is_active_no_current_build(self):
 
170
        """Test currentBuildIsActive returns False when currentBuild = False"""
 
171
 
 
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)
 
175
 
 
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()
 
180
 
 
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)
 
186
 
 
187
    def test_current_build_is_active_build_state_not_active(self):
 
188
        """Test currentBuildIsActive when currentBuild state != STATE_ACTIVE
 
189
 
 
190
        currentBuildIsActive should return False when
 
191
        slave.currentBuild.state != STATE_ACTIVE. Clearly, currentBuild needs
 
192
        to be valid for this test.
 
193
 
 
194
        """
 
195
 
 
196
        # Set up a fake build process (starts off in STATE_ACTIVE)
 
197
        self.slave.currentBuild = FakeProjectBuildProcess()
 
198
 
 
199
        self.slave.currentBuild.state = ProjectBuildProcess.STATE_SUCCESS
 
200
        self.assertEqual(self.slave.currentBuildIsActive(), False)
 
201
 
 
202
        self.slave.currentBuild.state = ProjectBuildProcess.STATE_FAILED
 
203
        self.assertEqual(self.slave.currentBuildIsActive(), False)
 
204
 
 
205
    def test_add_user_to_bzr_ssh_url(self):
 
206
        """Test lp:project URL form converted into bzr+ssh form adding user."""
 
207
 
 
208
        config_url = u"lp:~dooferlad/offspring/config"
 
209
        user = "user"
 
210
 
 
211
        updated_url = self.slave.addUserToBzrSshUrl(config_url, user)
 
212
        expected_url = ("bzr+ssh://user@bazaar.launchpad.net/~dooferlad/" +
 
213
                        "offspring/config")
 
214
        self.assertEqual(updated_url, expected_url)
 
215
 
 
216
    def test_start_build_with_custom_ssh_key(self):
 
217
        """Test slave converts lp:project URL into bzr+ssh form adding user.
 
218
 
 
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).
 
223
        """
 
224
        log_file = self.capture_logging()
 
225
        build_process_mock = self.mocker.patch(ProjectBuildProcess)
 
226
        build_process_mock.start()
 
227
 
 
228
        sshkey_filename = self.makeFile()
 
229
        tempfile_mock = self.mocker.replace("tempfile.NamedTemporaryFile")
 
230
        tempfile_mock()
 
231
        self.mocker.result(open(sshkey_filename, "w"))
 
232
 
 
233
        self.mocker.replay()
 
234
 
 
235
        config_url = u"lp:~dooferlad/offspring/config"
 
236
        args = ["project_name", config_url, "user", "key"]
 
237
 
 
238
        self.slave.startBuild(*args)
 
239
 
 
240
        new_url = re.sub("lp:",
 
241
                          "bzr+ssh://" + args[2] + "@bazaar.launchpad.net/",
 
242
                          args[1])
 
243
        self.assertEqual(["project_name", new_url, sshkey_filename],
 
244
                         self.slave.currentBuild.arguments)
 
245
        self.assertEqual(
 
246
            "Starting build: /usr/bin/offspring-build project_name %s\n"
 
247
            % new_url, log_file.getvalue())
 
248
 
 
249
    def test_check_ssh_key_deleted_when_build_finished(self):
 
250
        """Test SSH key file lifetime management.
 
251
 
 
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.
 
256
        """
 
257
        log_file = self.capture_logging()
 
258
        build_process_mock = self.mocker.patch(ProjectBuildProcess)
 
259
        build_process_mock.start()
 
260
        self.mocker.replay()
 
261
 
 
262
        config_url = u"lp:~dooferlad/offspring/config"
 
263
        args = ["project_name", config_url, "user", "key"]
 
264
 
 
265
        self.slave.startBuild(*args)
 
266
 
 
267
        file_path = self.slave.currentBuild.ssh_key_file.name
 
268
        read_key = open(file_path).read()
 
269
 
 
270
        # We should have a key written to a file for the slave to read...
 
271
        self.assertEqual(read_key, args[3])
 
272
 
 
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))
 
278
 
 
279
    def test_check_ssh_agent(self):
 
280
        """Check that SSH agent is started and killed by build.
 
281
 
 
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.
 
285
 
 
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.
 
289
 
 
290
        """
 
291
        # This is a wrapper around a shell script that runs the commands and
 
292
        # we just check the output.
 
293
 
 
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")
 
297
 
 
298
        test_script_process = subprocess.Popen(test_script,
 
299
                                               stdout=subprocess.PIPE,
 
300
                                               stderr=subprocess.STDOUT)
 
301
 
 
302
        stdout = test_script_process.communicate()[0]
 
303
 
 
304
        lines = stdout.split("\n")
 
305
 
 
306
        # Sample output:
 
307
        # Agent pid 9839
 
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.
 
313
 
 
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])
 
318
 
 
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",
 
322
                                  lines[1]),
 
323
                        "Test build ssh-agent check failed: Expected to find" +
 
324
                        "\nIdentity added: <path to...>/offspring_test_key" +
 
325
                        "\nfound:\n" + lines[1])
 
326
 
 
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])
 
333
 
 
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."
 
337
        search_string = (
 
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)
 
343
 
 
344
 
 
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
 
350
        self.stopped = False
 
351
        self.state = state
 
352
 
 
353
    def startBuild(self, project_name, config_url):
 
354
        self.project_name = project_name
 
355
        self.config_url = config_url
 
356
        self.started = True
 
357
 
 
358
    def stopBuild(self):
 
359
        self.stopped = True
 
360
 
 
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
 
365
        return False
 
366
 
 
367
 
 
368
class TestLexbuilderSlaveServer(LexbuilderSlaveTestMixin,
 
369
                                CaptureLoggingTestCase):
 
370
    """
 
371
    Tests for the LexbuilderSlaveServer class.
 
372
    """
 
373
 
 
374
    def setUp(self):
 
375
        super(TestLexbuilderSlaveServer, self).setUp()
 
376
        self.fake_slave = FakeLexbuilderSlave()
 
377
 
 
378
    def create_slave_server(self):
 
379
        """
 
380
        Instantiate a slave server for the fake_slave.
 
381
        """
 
382
        return LexbuilderSlaveServer(
 
383
            self.fake_slave, ("localhost", 8760), self.config)
 
384
 
 
385
    def test_create(self):
 
386
        """
 
387
        We can instantiate a new LexbuilderSlaveServer.
 
388
        """
 
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())
 
395
 
 
396
    def test_api_start_build(self):
 
397
        """
 
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.
 
401
        """
 
402
        self.fake_slave.currentBuild = FakeBuild()
 
403
        slave_server = self.create_slave_server()
 
404
 
 
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)
 
411
 
 
412
    def test_api_start_build_with_busy_server(self):
 
413
        """
 
414
        If the server has an active build in progress, api_startBuild should
 
415
        return an error state.
 
416
        """
 
417
        slave_server = self.create_slave_server()
 
418
 
 
419
        self.fake_slave.currentBuild = FakeBuild(
 
420
            state=ProjectBuildProcess.STATE_ACTIVE)
 
421
 
 
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)
 
427
 
 
428
    def test_api_stop_build(self):
 
429
        """
 
430
        If the SlaveServer has an active build, api_stopBuild should end the
 
431
        current build.
 
432
        """
 
433
        slave_server = self.create_slave_server()
 
434
        self.fake_slave.currentBuild = FakeBuild(
 
435
            state=ProjectBuildProcess.STATE_ACTIVE)
 
436
 
 
437
        response = slave_server.api_stopBuild()
 
438
        self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR, response)
 
439
        self.assertFalse(self.fake_slave.stopped)
 
440
 
 
441
    def test_api_stop_build_with_correct_platform_version(self):
 
442
        """
 
443
        If platform.version is > 2.6, and we have a current build, then we
 
444
        can stop the build slave.
 
445
 
 
446
        FIXME: Add documentation for why?
 
447
        """
 
448
        version_mock = self.mocker.replace("platform.version")
 
449
        version_mock()
 
450
        self.mocker.result("2.7")
 
451
        self.mocker.replay()
 
452
        slave_server = self.create_slave_server()
 
453
        self.fake_slave.currentBuild = FakeBuild(
 
454
            state=ProjectBuildProcess.STATE_ACTIVE)
 
455
 
 
456
        response = slave_server.api_stopBuild()
 
457
        self.assertEqual(LexbuilderSlaveServer.REQUEST_OKAY, response)
 
458
        self.assertTrue(self.fake_slave.stopped)
 
459
 
 
460
    def test_api_stop_build_no_current_build(self):
 
461
        """
 
462
        If the SlaveServer does not have an active build, api_stopBuild should
 
463
        return an error state.
 
464
        """
 
465
        slave_server = self.create_slave_server()
 
466
        self.fake_slave.currentBuild = FakeBuild()
 
467
 
 
468
        response = slave_server.api_stopBuild()
 
469
        self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR, response)
 
470
        self.assertFalse(self.fake_slave.stopped)
 
471
 
 
472
    def test_api_get_builder_name(self):
 
473
        """
 
474
        LexbuilderSlaveServer.api_getBuilderName should return the network name
 
475
        for this machine.
 
476
        """
 
477
        slave_server = self.create_slave_server()
 
478
        self.assertEqual(platform.node(), slave_server.api_getBuilderName())
 
479
 
 
480
    def test_api_get_machine_type(self):
 
481
        """
 
482
        LexbuilderSlaveServer.api_getMachineType should return the platform the
 
483
        builder is running on.
 
484
        """
 
485
        slave_server = self.create_slave_server()
 
486
        self.assertEqual(platform.machine(), slave_server.api_getMachineType())
 
487
 
 
488
    def test_api_get_builder_state(self):
 
489
        """
 
490
        LexbuilderSlaveServer.api_getBuilderState should return the current
 
491
        state of the underlying LexBuilderSlave.
 
492
        """
 
493
        slave_server = self.create_slave_server()
 
494
        self.assertEqual(LexbuilderSlave.STATE_IDLE,
 
495
                         slave_server.api_getBuilderState())
 
496
 
 
497
    def test_api_get_build_project_name(self):
 
498
        """
 
499
        LexbuilderSlaveServer.api_getBuildProjectName should return the name of
 
500
        the current build project.
 
501
        """
 
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())
 
506
 
 
507
    def test_api_get_build_project_name_with_no_active_build(self):
 
508
        """
 
509
        LexbuilderSlaveServer.api_getBuildProjectName should return an error
 
510
        state if there's no current build project.
 
511
        """
 
512
        slave_server = self.create_slave_server()
 
513
        self.fake_slave.currentBuild = None
 
514
        self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR,
 
515
                         slave_server.api_getBuildProjectName())
 
516
 
 
517
    def test_api_get_build_name(self):
 
518
        """
 
519
        LexbuilderSlaveServer.getBuildName should return the name of the
 
520
        current build.
 
521
        """
 
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())
 
526
 
 
527
    def test_api_get_build_name_with_no_active_build(self):
 
528
        """
 
529
        LexbuilderSlaveServer.api_getBuildName should return an error
 
530
        state if there's no current build project.
 
531
        """
 
532
        slave_server = self.create_slave_server()
 
533
        self.fake_slave.currentBuild = None
 
534
        self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR,
 
535
                         slave_server.api_getBuildName())
 
536
 
 
537
    def test_api_get_build_result(self):
 
538
        """
 
539
        LexbuilderSlaveServer.getBuildResult should return the result of the
 
540
        current build.
 
541
        """
 
542
        slave_server = self.create_slave_server()
 
543
        self.fake_slave.currentBuild = FakeBuild()
 
544
        self.assertEqual(ProjectBuildProcess.STATE_SUCCESS,
 
545
                         slave_server.api_getBuildResult())
 
546
 
 
547
    def test_api_get_build_result_with_no_active_build(self):
 
548
        """
 
549
        LexbuilderSlaveServer.api_getBuildResult should return an error
 
550
        state if there's no current build.
 
551
        """
 
552
        slave_server = self.create_slave_server()
 
553
        self.fake_slave.currentBuild = None
 
554
        self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR,
 
555
                         slave_server.api_getBuildResult())
 
556
 
 
557
    def test_api_get_build_started_at(self):
 
558
        """
 
559
        LexbuilderSlaveServer.getBuildStartedAt should return the time the
 
560
        current build (if any) started at, using the xmlrpc DateTime
 
561
        serialiser.
 
562
        """
 
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())
 
568
 
 
569
    def test_api_get_build_started_at_with_no_active_build(self):
 
570
        """
 
571
        LexbuilderSlaveServer.api_getBuildStartedAt should return an error
 
572
        state if there's no current build.
 
573
        """
 
574
        slave_server = self.create_slave_server()
 
575
        self.fake_slave.currentBuild = None
 
576
        self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR,
 
577
                         slave_server.api_getBuildStartedAt())
 
578
 
 
579
    def test_api_get_build_started_at_with_no_start_time(self):
 
580
        """
 
581
        LexbuilderSlaveServer.api_getBuildStartedAt should return an error if
 
582
        the start time of the current build is unknown.
 
583
        """
 
584
        slave_server = self.create_slave_server()
 
585
        self.fake_slave.currentBuild = FakeBuild()
 
586
        self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR,
 
587
                         slave_server.api_getBuildStartedAt())
 
588
 
 
589
    def test_api_get_build_finished_at(self):
 
590
        """
 
591
        LexbuilderSlaveServer.getBuildFinishedAt should return the time the
 
592
        current build (if any) finished at, using the xmlrpc DateTime
 
593
        serialiser.
 
594
        """
 
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())
 
600
 
 
601
    def test_api_get_build_finished_at_with_no_active_build(self):
 
602
        """
 
603
        LexbuilderSlaveServer.api_getBuildFinishedAt should return an error
 
604
        state if there's no current build.
 
605
        """
 
606
        slave_server = self.create_slave_server()
 
607
        self.fake_slave.currentBuild = None
 
608
        self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR,
 
609
                         slave_server.api_getBuildFinishedAt())
 
610
 
 
611
    def test_api_get_build_finished_at_with_no_finish_time(self):
 
612
        """
 
613
        LexbuilderSlaveServer.api_getBuildFinishedAt should return an error if
 
614
        the finish time of the current build is unknown.
 
615
        """
 
616
        slave_server = self.create_slave_server()
 
617
        self.fake_slave.currentBuild = FakeBuild()
 
618
        self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR,
 
619
                         slave_server.api_getBuildFinishedAt())
 
620
 
 
621
    def test_api_get_build_error_code(self):
 
622
        """
 
623
        LexbuilderSlaveServer.getBuildErrorCode should return the current error
 
624
        code of the build.
 
625
        """
 
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())
 
630
 
 
631
    def test_api_get_build_error_code_with_no_active_build(self):
 
632
        """
 
633
        LexbuilderSlaveServer.api_getBuildErrorCode should return an error
 
634
        state if there's no current build.
 
635
        """
 
636
        slave_server = self.create_slave_server()
 
637
        self.fake_slave.currentBuild = None
 
638
        self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR,
 
639
                         slave_server.api_getBuildErrorCode())
 
640
 
 
641
    def test_api_get_build_error_code_with_no_finish_time(self):
 
642
        """
 
643
        LexbuilderSlaveServer.api_getBuildErrorCode should return an error if
 
644
        the error code of the current build is unknown.
 
645
        """
 
646
        slave_server = self.create_slave_server()
 
647
        self.fake_slave.currentBuild = FakeBuild()
 
648
        self.assertEqual(LexbuilderSlaveServer.REQUEST_ERROR,
 
649
                         slave_server.api_getBuildErrorCode())
 
650
 
 
651
    def test_api_shutdown_slave_not_idle(self):
 
652
        """
 
653
        LexbuilderSlaveServer.api_shutdown should return an error if the slave
 
654
        is not idle.
 
655
        """
 
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())
 
661
 
 
662
    def test_api_shutdown_slave_idle(self):
 
663
        """
 
664
        LexbuilderSlaveServer.api_shutdown should start a shutdown thread if
 
665
        the slave is currently idle.
 
666
        """
 
667
        class FakeShutdownThread(object):
 
668
            def start(self):
 
669
                self.started = True
 
670
        slave_server = self.create_slave_server()
 
671
        self.fake_slave.currentBuild = FakeBuild()
 
672
 
 
673
        mock_shutdown_thread = self.mocker.replace(ShutdownThread,
 
674
                                                   passthrough=False)
 
675
        mock_shutdown_thread(slave_server)
 
676
        fake_shutdown = FakeShutdownThread()
 
677
        self.mocker.result(fake_shutdown)
 
678
        self.mocker.replay()
 
679
 
 
680
        self.assertEqual(LexbuilderSlaveServer.REQUEST_OKAY,
 
681
                         slave_server.api_shutdown())
 
682
        self.assertTrue(fake_shutdown.started)