blob: 93b994c37dc2a62a8a5d45bef787d60081988530 [file] [log] [blame]
Gabe Black3b567202015-09-23 14:07:59 -07001#!/usr/bin/python2
Chris Sosa7cd23202013-10-15 17:22:57 -07002
3# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Integration tests for the devserver.
8
9This module is responsible for testing the actual devserver APIs and should be
10run whenever changes are made to the devserver.
11
12Note there are two classes of tests here and they can be run separately.
13
14To just run the short-running "unittests" run:
15 ./devserver_integration_tests.py DevserverUnittests
16
17To just run the longer-running tests, run:
18 ./devserver_integration_tests.py DevserverIntegrationTests
19"""
20
Gabe Black3b567202015-09-23 14:07:59 -070021from __future__ import print_function
22
Chris Sosa7cd23202013-10-15 17:22:57 -070023import devserver_constants
24import json
25import logging
26from xml.dom import minidom
27import os
28import psutil
29import shutil
30import signal
Gilad Arnold7de05f72014-02-14 13:14:20 -080031import socket
Chris Sosa7cd23202013-10-15 17:22:57 -070032import subprocess
33import tempfile
34import time
35import unittest
36import urllib2
37
38
39# Paths are relative to this script's base directory.
40LABEL = 'devserver'
41TEST_IMAGE_PATH = 'testdata/devserver'
42TEST_IMAGE_NAME = 'update.gz'
43EXPECTED_HASH = 'kGcOinJ0vA8vdYX53FN0F5BdwfY='
44
45# Update request based on Omaha v3 protocol format.
46UPDATE_REQUEST = """<?xml version="1.0" encoding="UTF-8"?>
47<request version="ChromeOSUpdateEngine-0.1.0.0" updaterversion="ChromeOSUpdateEngine-0.1.0.0" protocol="3.0" ismachine="1">
48 <os version="Indy" platform="Chrome OS" sp="0.11.254.2011_03_09_1814_i686"></os>
49 <app appid="{DEV-BUILD}" version="11.254.2011_03_09_1814" lang="en-US" track="developer-build" board="x86-generic" hardware_class="BETA DVT" delta_okay="true">
50 <updatecheck></updatecheck>
51 </app>
52</request>
53"""
54
55# RPC constants.
56STAGE = 'stage'
57IS_STAGED = 'is_staged'
58STATIC = 'static'
59UPDATE = 'update'
60CHECK_HEALTH = 'check_health'
61CONTROL_FILES = 'controlfiles'
62XBUDDY = 'xbuddy'
Prashanth Ba06d2d22014-03-07 15:35:19 -080063LIST_IMAGE_DIR = 'list_image_dir'
Chris Sosa7cd23202013-10-15 17:22:57 -070064
65# API rpcs and constants.
66API_HOST_INFO = 'api/hostinfo'
67API_SET_NEXT_UPDATE = 'api/setnextupdate'
68API_SET_UPDATE_REQUEST = 'new_update-test/the-new-update'
69API_TEST_IP_ADDR = '127.0.0.1'
70
71DEVSERVER_START_TIMEOUT = 15
Gilad Arnold08516112014-02-14 13:14:03 -080072DEVSERVER_START_SLEEP = 1
Gilad Arnold7de05f72014-02-14 13:14:20 -080073MAX_START_ATTEMPTS = 5
Chris Sosa7cd23202013-10-15 17:22:57 -070074
75
76class DevserverFailedToStart(Exception):
77 """Raised if we could not start the devserver."""
78
79
Gilad Arnold08516112014-02-14 13:14:03 -080080class DevserverTestBase(unittest.TestCase):
Chris Sosa7cd23202013-10-15 17:22:57 -070081 """Class containing common logic between devserver test classes."""
82
83 def setUp(self):
Gilad Arnold08516112014-02-14 13:14:03 -080084 """Creates and populates a test directory, temporary files."""
Chris Sosa7cd23202013-10-15 17:22:57 -070085 self.test_data_path = tempfile.mkdtemp()
86 self.src_dir = os.path.dirname(__file__)
87
88 # Current location of testdata payload.
89 image_src = os.path.join(self.src_dir, TEST_IMAGE_PATH, TEST_IMAGE_NAME)
90
Gilad Arnold08516112014-02-14 13:14:03 -080091 # Copy the payload to the location of the update label.
92 self._CreateLabelAndCopyImage(LABEL, image_src)
Chris Sosa7cd23202013-10-15 17:22:57 -070093
94 # Copy the payload to the location of forced label.
Gilad Arnold08516112014-02-14 13:14:03 -080095 self._CreateLabelAndCopyImage(API_SET_UPDATE_REQUEST, image_src)
Chris Sosa7cd23202013-10-15 17:22:57 -070096
Gilad Arnold08516112014-02-14 13:14:03 -080097 # Allocate temporary files for various devserver outputs.
98 self.pidfile = self._MakeTempFile('pid')
99 self.portfile = self._MakeTempFile('port')
100 self.logfile = self._MakeTempFile('log')
Chris Sosa7cd23202013-10-15 17:22:57 -0700101
Gilad Arnold08516112014-02-14 13:14:03 -0800102 # Initialize various runtime values.
103 self.devserver_url = self.port = self.pid = None
Chris Sosa7cd23202013-10-15 17:22:57 -0700104
105 def tearDown(self):
Gilad Arnold08516112014-02-14 13:14:03 -0800106 """Kill the server, remove the test directory and temporary files."""
Chris Sosa7cd23202013-10-15 17:22:57 -0700107 if self.pid:
108 os.kill(self.pid, signal.SIGKILL)
109
Gilad Arnold08516112014-02-14 13:14:03 -0800110 self._RemoveFile(self.pidfile)
111 self._RemoveFile(self.portfile)
112 self._RemoveFile(self.logfile)
113 shutil.rmtree(self.test_data_path)
Chris Sosa7cd23202013-10-15 17:22:57 -0700114
115 # Helper methods begin here.
116
Gilad Arnold08516112014-02-14 13:14:03 -0800117 def _CreateLabelAndCopyImage(self, label, image):
118 """Creates a label location and copies an image to it."""
119 label_dir = os.path.join(self.test_data_path, label)
120 os.makedirs(label_dir)
121 shutil.copy(image, os.path.join(label_dir, TEST_IMAGE_NAME))
122
123 def _MakeTempFile(self, suffix):
124 """Return path of a newly created temporary file."""
125 with tempfile.NamedTemporaryFile(suffix='-devserver-%s' % suffix) as f:
126 name = f.name
127 f.close()
128
129 return name
130
131 def _RemoveFile(self, filename):
132 """Removes a file if it is present."""
133 if os.path.isfile(filename):
134 os.remove(filename)
135
136 def _ReadIntValueFromFile(self, path, desc):
137 """Reads a string from file and returns its conversion into an integer."""
138 if not os.path.isfile(path):
139 raise DevserverFailedToStart('Devserver did not drop %s (%r).' %
140 (desc, path))
141
142 with open(path) as f:
143 value_str = f.read()
144
145 try:
146 return int(value_str)
147 except ValueError:
148 raise DevserverFailedToStart('Devserver did not drop a valid value '
149 'in %s (%r).' % (desc, value_str))
150
151 def _StartServer(self, port=0):
Chris Sosa7cd23202013-10-15 17:22:57 -0700152 """Attempts to start devserver on |port|.
153
Gilad Arnold08516112014-02-14 13:14:03 -0800154 In the default case where port == 0, the server will bind to an arbitrary
Dan Shi2f136862016-02-11 15:38:38 -0800155 available port. If successful, this method will set the devserver's pid
Gilad Arnold08516112014-02-14 13:14:03 -0800156 (self.pid), actual listening port (self.port) and URL (self.devserver_url).
Chris Sosa7cd23202013-10-15 17:22:57 -0700157
158 Raises:
159 DevserverFailedToStart: If the devserver could not be started.
160 """
161 cmd = [
162 'python',
163 os.path.join(self.src_dir, 'devserver.py'),
164 'devserver.py',
165 '--static_dir', self.test_data_path,
166 '--pidfile', self.pidfile,
Gilad Arnold08516112014-02-14 13:14:03 -0800167 '--portfile', self.portfile,
Chris Sosa7cd23202013-10-15 17:22:57 -0700168 '--port', str(port),
169 '--logfile', self.logfile]
170
171 # Pipe all output. Use logfile to get devserver log.
172 subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
173
Gilad Arnold08516112014-02-14 13:14:03 -0800174 # Wait for devserver to start, determining its actual serving port and URL.
Chris Sosa7cd23202013-10-15 17:22:57 -0700175 current_time = time.time()
176 deadline = current_time + DEVSERVER_START_TIMEOUT
177 while current_time < deadline:
Chris Sosa7cd23202013-10-15 17:22:57 -0700178 try:
Gilad Arnold08516112014-02-14 13:14:03 -0800179 self.port = self._ReadIntValueFromFile(self.portfile, 'portfile')
180 self.devserver_url = 'http://127.0.0.1:%d' % self.port
Gabe Black3b567202015-09-23 14:07:59 -0700181 self._MakeRPC(CHECK_HEALTH, timeout=1)
Chris Sosa7cd23202013-10-15 17:22:57 -0700182 break
183 except Exception:
Gilad Arnold08516112014-02-14 13:14:03 -0800184 time.sleep(DEVSERVER_START_SLEEP)
185 current_time = time.time()
Chris Sosa7cd23202013-10-15 17:22:57 -0700186 else:
187 raise DevserverFailedToStart('Devserver failed to start within timeout.')
188
Gilad Arnold08516112014-02-14 13:14:03 -0800189 # Retrieve PID.
190 self.pid = self._ReadIntValueFromFile(self.pidfile, 'pidfile')
Chris Sosa7cd23202013-10-15 17:22:57 -0700191
192 def VerifyHandleUpdate(self, label, use_test_payload=True):
193 """Verifies that we can send an update request to the devserver.
194
195 This method verifies (using a fake update_request blob) that the devserver
196 can interpret the payload and give us back the right payload.
197
198 Args:
199 label: Label that update is served from e.g. <board>-release/<version>
200 use_test_payload: If set to true, expects to serve payload under
201 testdata/ and does extra checks i.e. compares hash and content of
202 payload.
Gilad Arnold08516112014-02-14 13:14:03 -0800203
Chris Sosa7cd23202013-10-15 17:22:57 -0700204 Returns:
205 url of the update payload if we verified the update.
206 """
207 update_label = '/'.join([UPDATE, label])
208 response = self._MakeRPC(update_label, data=UPDATE_REQUEST)
209 self.assertNotEqual('', response)
210
211 # Parse the response and check if it contains the right result.
212 dom = minidom.parseString(response)
213 update = dom.getElementsByTagName('updatecheck')[0]
214 expected_static_url = '/'.join([self.devserver_url, STATIC, label])
215 expected_hash = EXPECTED_HASH if use_test_payload else None
216 url = self.VerifyV3Response(update, expected_static_url,
217 expected_hash=expected_hash)
218
219 # Verify the image we download is correct since we already know what it is.
220 if use_test_payload:
221 connection = urllib2.urlopen(url)
222 contents = connection.read()
223 connection.close()
224 self.assertEqual('Developers, developers, developers!\n', contents)
225
226 return url
227
228 def VerifyV3Response(self, update, expected_static_url, expected_hash):
229 """Verifies the update DOM from a v3 response and returns the url."""
230 # Parse the response and check if it contains the right result.
231 urls = update.getElementsByTagName('urls')[0]
232 url = urls.getElementsByTagName('url')[0]
233
234 static_url = url.getAttribute('codebase')
235 # Static url's end in /.
236 self.assertEqual(expected_static_url + '/', static_url)
237
238 manifest = update.getElementsByTagName('manifest')[0]
239 packages = manifest.getElementsByTagName('packages')[0]
240 package = packages.getElementsByTagName('package')[0]
241 filename = package.getAttribute('name')
242 self.assertEqual(TEST_IMAGE_NAME, filename)
243
244 if expected_hash:
245 hash_value = package.getAttribute('hash')
246 self.assertEqual(EXPECTED_HASH, hash_value)
247
248 url = os.path.join(static_url, filename)
249 return url
250
251 def _MakeRPC(self, rpc, data=None, timeout=None, **kwargs):
Gilad Arnold08516112014-02-14 13:14:03 -0800252 """Makes an RPC call to the devserver.
Chris Sosa7cd23202013-10-15 17:22:57 -0700253
254 Args:
Gilad Arnold08516112014-02-14 13:14:03 -0800255 rpc: The function to run on the devserver, e.g. 'stage'.
Chris Sosa7cd23202013-10-15 17:22:57 -0700256 data: Optional post data to send.
257 timeout: Optional timeout to pass to urlopen.
Gilad Arnold08516112014-02-14 13:14:03 -0800258 kwargs: Optional arguments to the function, e.g. artifact_url='foo/bar'.
Chris Sosa7cd23202013-10-15 17:22:57 -0700259
Gilad Arnold08516112014-02-14 13:14:03 -0800260 Returns:
261 The function output.
Chris Sosa7cd23202013-10-15 17:22:57 -0700262 """
263 request = '/'.join([self.devserver_url, rpc])
264 if kwargs:
265 # Join the kwargs to the URL.
266 request += '?' + '&'.join('%s=%s' % item for item in kwargs.iteritems())
267
268 output = None
269 try:
270 # Let's log output for all rpc's without timeouts because we only
271 # use timeouts to check to see if something is up and these checks tend
272 # to be small and so logging it will be extremely repetitive.
273 if not timeout:
274 logging.info('Making request using %s', request)
275
276 connection = urllib2.urlopen(request, data=data, timeout=timeout)
277 output = connection.read()
278 connection.close()
279 except urllib2.HTTPError:
280 raise
281
282 return output
283
284
Gilad Arnold08516112014-02-14 13:14:03 -0800285class AutoStartDevserverTestBase(DevserverTestBase):
286 """Test base class that automatically starts the devserver."""
287
288 def setUp(self):
289 """Initialize everything, then start the server."""
290 super(AutoStartDevserverTestBase, self).setUp()
291 self._StartServer()
292
293
Gilad Arnold7de05f72014-02-14 13:14:20 -0800294class DevserverStartTests(DevserverTestBase):
295 """Test that devserver starts up correctly."""
296
297 def testStartAnyPort(self):
298 """Starts the devserver, have it bind to an arbitrary available port."""
299 self._StartServer()
300
301 def testStartSpecificPort(self):
302 """Starts the devserver with a specific port."""
303 for _ in range(MAX_START_ATTEMPTS):
304 # This is a cheap hack to find an arbitrary unused port: we open a socket
305 # and bind it to port zero, then pull out the actual port number and
306 # close the socket. In all likelihood, this will leave us with an
307 # available port number that we can use for starting the devserver.
308 # However, this heuristic is susceptible to race conditions, hence the
309 # retry loop.
310 s = socket.socket()
311 s.bind(('', 0))
312 # s.getsockname() is definitely callable.
313 # pylint: disable=E1102
314 _, port = s.getsockname()
315 s.close()
316
317 self._StartServer(port=port)
318
319
Gilad Arnold08516112014-02-14 13:14:03 -0800320class DevserverBasicTests(AutoStartDevserverTestBase):
321 """Short running tests for the devserver (no remote deps).
Chris Sosa7cd23202013-10-15 17:22:57 -0700322
323 These are technically not unittests because they depend on being able to
324 start a devserver locally which technically requires external resources so
325 they are lumped with the remote tests here.
326 """
327
328 def testHandleUpdateV3(self):
329 self.VerifyHandleUpdate(label=LABEL)
330
331 def testApiBadSetNextUpdateRequest(self):
332 """Tests sending a bad setnextupdate request."""
333 # Send bad request and ensure it fails...
334 self.assertRaises(urllib2.URLError,
335 self._MakeRPC,
336 '/'.join([API_SET_NEXT_UPDATE, API_TEST_IP_ADDR]))
337
338 def testApiBadSetNextUpdateURL(self):
339 """Tests contacting a bad setnextupdate url."""
340 # Send bad request and ensure it fails...
341 self.assertRaises(urllib2.URLError,
342 self._MakeRPC, API_SET_NEXT_UPDATE)
343
344 def testApiBadHostInfoURL(self):
345 """Tests contacting a bad hostinfo url."""
346 # Host info should be invalid without a specified address.
347 self.assertRaises(urllib2.URLError,
348 self._MakeRPC, API_HOST_INFO)
349
350 def testApiHostInfoAndSetNextUpdate(self):
351 """Tests using the setnextupdate and hostinfo api commands."""
352 # Send setnextupdate command.
353 self._MakeRPC('/'.join([API_SET_NEXT_UPDATE, API_TEST_IP_ADDR]),
354 data=API_SET_UPDATE_REQUEST)
355
356 # Send hostinfo command and verify the setnextupdate worked.
357 response = self._MakeRPC('/'.join([API_HOST_INFO, API_TEST_IP_ADDR]))
358
359 self.assertEqual(
360 json.loads(response)['forced_update_label'], API_SET_UPDATE_REQUEST)
361
362 def testXBuddyLocalAlias(self):
363 """Extensive local image xbuddy unittest.
364
365 This test verifies all the local xbuddy logic by creating a new local folder
366 with the necessary update items and verifies we can use all of them.
367 """
368 update_data = 'TEST UPDATE'
369 image_data = 'TEST IMAGE'
370 stateful_data = 'STATEFUL STUFFS'
371 build_id = 'x86-generic/R32-9999.0.0-a1'
372 xbuddy_path = 'x86-generic/R32-9999.0.0-a1/test'
373 build_dir = os.path.join(self.test_data_path, build_id)
374 os.makedirs(build_dir)
375 test_image_file = os.path.join(build_dir,
376 devserver_constants.TEST_IMAGE_FILE)
377 update_file = os.path.join(build_dir, devserver_constants.UPDATE_FILE)
378 stateful_file = os.path.join(build_dir, devserver_constants.STATEFUL_FILE)
379
380 logging.info('Creating dummy files')
381
382 for item, filename, data in zip(
383 ['full_payload', 'test', 'stateful'],
384 [update_file, test_image_file, stateful_file],
385 [update_data, image_data, stateful_data]):
386 logging.info('Creating file %s', filename)
387 with open(filename, 'w') as fh:
388 fh.write(data)
389
390 xbuddy_path = '/'.join([build_id, item])
391 logging.info('Testing xbuddy path %s', xbuddy_path)
392 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]))
393 self.assertEqual(response, data)
394
395 expected_dir = '/'.join([self.devserver_url, STATIC, build_id])
396 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]), return_dir=True)
397 self.assertEqual(response, expected_dir)
398
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800399 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]),
400 relative_path=True)
401 self.assertEqual(response, build_id)
402
Chris Sosa7cd23202013-10-15 17:22:57 -0700403 xbuddy_path = '/'.join([build_id, 'test'])
404 logging.info('Testing for_update for %s', xbuddy_path)
405 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]), for_update=True)
406 expected_path = '/'.join([self.devserver_url, UPDATE, build_id])
407 self.assertTrue(response, expected_path)
408
409 logging.info('Verifying the actual payload data')
410 url = self.VerifyHandleUpdate(build_id, use_test_payload=False)
411 logging.info('Verify the actual content of the update payload')
412 connection = urllib2.urlopen(url)
413 contents = connection.read()
414 connection.close()
415 self.assertEqual(update_data, contents)
416
417 def testPidFile(self):
418 """Test that using a pidfile works correctly."""
419 with open(self.pidfile, 'r') as f:
420 pid = f.read()
Chris Sosa7cd23202013-10-15 17:22:57 -0700421 # Let's assert some process information about the devserver.
Dan Shi2f136862016-02-11 15:38:38 -0800422 self.assertTrue(pid.strip().isdigit())
Chris Sosa7cd23202013-10-15 17:22:57 -0700423 process = psutil.Process(int(pid))
424 self.assertTrue(process.is_running())
425 self.assertTrue('devserver.py' in process.cmdline)
426
427
Gilad Arnold08516112014-02-14 13:14:03 -0800428class DevserverExtendedTests(AutoStartDevserverTestBase):
Chris Sosa7cd23202013-10-15 17:22:57 -0700429 """Longer running integration tests that test interaction with Google Storage.
430
431 Note: due to the interaction with Google Storage, these tests both require
432 1) runner has access to the Google Storage bucket where builders store builds.
433 2) time. These tests actually download the artifacts needed.
434 """
435
436 def testStageAndUpdate(self):
437 """Tests core autotest workflow where we stage/update with a test payload.
Gabe Black3b567202015-09-23 14:07:59 -0700438
Chris Sosa7cd23202013-10-15 17:22:57 -0700439 """
Dan Shi2f136862016-02-11 15:38:38 -0800440 build_id = 'peppy-release/R50-7870.0.0'
Chris Sosa7cd23202013-10-15 17:22:57 -0700441 archive_url = 'gs://chromeos-image-archive/%s' % build_id
442
443 response = self._MakeRPC(IS_STAGED, archive_url=archive_url,
444 artifacts='full_payload,stateful')
445 self.assertEqual(response, 'False')
446
447 logging.info('Staging update artifacts')
448 self._MakeRPC(STAGE, archive_url=archive_url,
449 artifacts='full_payload,stateful')
450 logging.info('Staging complete. '
451 'Verifying files exist and are staged in the staging '
452 'directory.')
453 response = self._MakeRPC(IS_STAGED, archive_url=archive_url,
454 artifacts='full_payload,stateful')
455 self.assertEqual(response, 'True')
456 staged_dir = os.path.join(self.test_data_path, build_id)
457 self.assertTrue(os.path.isdir(staged_dir))
458 self.assertTrue(os.path.exists(
459 os.path.join(staged_dir, devserver_constants.UPDATE_FILE)))
460 self.assertTrue(os.path.exists(
461 os.path.join(staged_dir, devserver_constants.STATEFUL_FILE)))
462
463 logging.info('Verifying we can update using the stage update artifacts.')
464 self.VerifyHandleUpdate(build_id, use_test_payload=False)
465
466 def testStageAutotestAndGetPackages(self):
467 """Another autotest workflow test where we stage/update with a test payload.
Gabe Black3b567202015-09-23 14:07:59 -0700468
Chris Sosa7cd23202013-10-15 17:22:57 -0700469 """
470 build_id = 'x86-mario-release/R32-4810.0.0'
471 archive_url = 'gs://chromeos-image-archive/%s' % build_id
472 autotest_artifacts = 'autotest,test_suites,au_suite'
473 logging.info('Staging autotest artifacts (may take a while).')
474 self._MakeRPC(STAGE, archive_url=archive_url, artifacts=autotest_artifacts)
475
476 response = self._MakeRPC(IS_STAGED, archive_url=archive_url,
477 artifacts=autotest_artifacts)
478 self.assertEqual(response, 'True')
479
480 # Verify the files exist and are staged in the staging directory.
481 logging.info('Checking directories exist after we staged the files.')
482 staged_dir = os.path.join(self.test_data_path, build_id)
483 autotest_dir = os.path.join(staged_dir, 'autotest')
484 package_dir = os.path.join(autotest_dir, 'packages')
485 self.assertTrue(os.path.isdir(staged_dir))
486 self.assertTrue(os.path.isdir(autotest_dir))
487 self.assertTrue(os.path.isdir(package_dir))
488
489 control_files = self._MakeRPC(CONTROL_FILES, build=build_id,
490 suite_name='bvt')
491 logging.info('Checking for known control file in bvt suite.')
492 self.assertTrue('client/site_tests/platform_FilePerms/'
493 'control' in control_files)
494
495 def testRemoteXBuddyAlias(self):
496 """Another autotest workflow test where we stage/update with a test payload.
Gabe Black3b567202015-09-23 14:07:59 -0700497
Chris Sosa7cd23202013-10-15 17:22:57 -0700498 """
499 build_id = 'x86-mario-release/R32-4810.0.0'
500 xbuddy_path = 'remote/x86-mario/R32-4810.0.0/full_payload'
501 xbuddy_bad_path = 'remote/x86-mario/R32-9999.9999.9999'
502 logging.info('Staging artifacts using xbuddy.')
503 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]), return_dir=True)
504
505 logging.info('Verifying static url returned is valid.')
506 expected_static_url = '/'.join([self.devserver_url, STATIC, build_id])
507 self.assertEqual(response, expected_static_url)
508
509 logging.info('Checking for_update returns an update_url for what we just '
510 'staged.')
511 expected_update_url = '/'.join([self.devserver_url, UPDATE, build_id])
512 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]), for_update=True)
513 self.assertEqual(response, expected_update_url)
514
515 logging.info('Now give xbuddy a bad path.')
516 self.assertRaises(urllib2.HTTPError,
517 self._MakeRPC,
518 '/'.join([XBUDDY, xbuddy_bad_path]))
519
Prashanth Ba06d2d22014-03-07 15:35:19 -0800520 def testListImageDir(self):
521 """Verifies that we can list the contents of the image directory."""
522 build_id = 'x86-mario-release/R32-4810.0.0'
523 archive_url = 'gs://chromeos-image-archive/%s' % build_id
524 build_dir = os.path.join(self.test_data_path, build_id)
525 shutil.rmtree(build_dir, ignore_errors=True)
526
527 logging.info('checking for %s on an unstaged build.', LIST_IMAGE_DIR)
528 response = self._MakeRPC(LIST_IMAGE_DIR, archive_url=archive_url)
529 self.assertTrue(archive_url in response and 'not been staged' in response)
530
531 logging.info('Checking for %s on a staged build.', LIST_IMAGE_DIR)
532 fake_file_name = 'fake_file'
533 try:
534 os.makedirs(build_dir)
535 open(os.path.join(build_dir, fake_file_name), 'w').close()
536 except OSError:
537 logging.error('Could not create files to imitate staged content. '
538 'Build dir %s, file %s', build_dir, fake_file_name)
539 raise
540 response = self._MakeRPC(LIST_IMAGE_DIR, archive_url=archive_url)
541 self.assertTrue(fake_file_name in response)
542 shutil.rmtree(build_dir, ignore_errors=True)
Chris Sosa7cd23202013-10-15 17:22:57 -0700543
544if __name__ == '__main__':
545 logging_format = '%(levelname)-8s: %(message)s'
546 logging.basicConfig(level=logging.DEBUG, format=logging_format)
547 unittest.main()