blob: 4605e34f0b9658e11aa303d642365742d89b3c1c [file] [log] [blame]
Girtsdba6ab22010-10-11 15:53:52 -07001#!/usr/bin/python
2
3# Copyright (c) 2010 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"""Regression tests for devserver."""
8
Gilad Arnoldabb352e2012-09-23 01:24:27 -07009from xml.dom import minidom
Dale Curtisc9aaf3a2011-08-09 15:47:40 -070010import json
Girtsdba6ab22010-10-11 15:53:52 -070011import os
Girtsdba6ab22010-10-11 15:53:52 -070012import shutil
Gilad Arnoldabb352e2012-09-23 01:24:27 -070013import signal
Girtsdba6ab22010-10-11 15:53:52 -070014import subprocess
15import sys
16import time
17import unittest
18import urllib2
Gilad Arnoldabb352e2012-09-23 01:24:27 -070019
Girtsdba6ab22010-10-11 15:53:52 -070020
21# Paths are relative to this script's base directory.
22STATIC_DIR = 'static'
Zdenek Behan5d21a2a2011-02-12 02:06:01 +010023TEST_IMAGE_PATH = 'testdata/devserver'
24TEST_IMAGE_NAME = 'developer-test.gz'
25TEST_IMAGE = TEST_IMAGE_PATH + '/' + TEST_IMAGE_NAME
Girtsdba6ab22010-10-11 15:53:52 -070026TEST_FACTORY_CONFIG = 'testdata/devserver/miniomaha-test.conf'
Zdenek Behan5d21a2a2011-02-12 02:06:01 +010027TEST_DATA_PATH = '/tmp/devserver-test'
Greg Spencerc8b59b22011-03-15 14:15:23 -070028TEST_CLIENT_PREFIX = 'ChromeOSUpdateEngine'
Jay Srinivasanac69d262012-10-30 19:05:53 -070029EXPECTED_HASH = 'kGcOinJ0vA8vdYX53FN0F5BdwfY='
Girtsdba6ab22010-10-11 15:53:52 -070030
Jay Srinivasanac69d262012-10-30 19:05:53 -070031# Update request based on Omaha v2 protocol format.
32UPDATE_REQUEST = {}
33UPDATE_REQUEST['2.0'] = """<?xml version="1.0" encoding="UTF-8"?>
Greg Spencerc8b59b22011-03-15 14:15:23 -070034<o:gupdate xmlns:o="http://www.google.com/update2/request" version="ChromeOSUpdateEngine-0.1.0.0" updaterversion="ChromeOSUpdateEngine-0.1.0.0" protocol="2.0" ismachine="1">
35 <o:os version="Indy" platform="Chrome OS" sp="0.11.254.2011_03_09_1814_i686"></o:os>
36 <o:app appid="{DEV-BUILD}" version="0.11.254.2011_03_09_1814" lang="en-US" track="developer-build" board="x86-generic" hardware_class="BETA DVT" delta_okay="true">
37 <o:updatecheck></o:updatecheck>
38 <o:event eventtype="3" eventresult="2" previousversion="0.11.216.2011_03_02_1358"></o:event>
39 </o:app>
Girtsdba6ab22010-10-11 15:53:52 -070040</o:gupdate>
41"""
Jay Srinivasanac69d262012-10-30 19:05:53 -070042
43# Update request based on Omaha v3 protocol format.
44UPDATE_REQUEST['3.0'] = """<?xml version="1.0" encoding="UTF-8"?>
45<request version="ChromeOSUpdateEngine-0.1.0.0" updaterversion="ChromeOSUpdateEngine-0.1.0.0" protocol="3.0" ismachine="1">
46 <os version="Indy" platform="Chrome OS" sp="0.11.254.2011_03_09_1814_i686"></os>
47 <app appid="{DEV-BUILD}" version="0.11.254.2011_03_09_1814" lang="en-US" track="developer-build" board="x86-generic" hardware_class="BETA DVT" delta_okay="true">
48 <updatecheck></updatecheck>
49 <event eventtype="3" eventresult="2" previousversion="0.11.216.2011_03_02_1358"></event>
50 </app>
51</request>
52"""
Girtsdba6ab22010-10-11 15:53:52 -070053# TODO(girts): use a random available port.
54UPDATE_URL = 'http://127.0.0.1:8080/update'
Jay Srinivasanac69d262012-10-30 19:05:53 -070055STATIC_URL = 'http://127.0.0.1:8080/static/'
Girtsdba6ab22010-10-11 15:53:52 -070056
Dale Curtisc9aaf3a2011-08-09 15:47:40 -070057API_HOST_INFO_BAD_URL = 'http://127.0.0.1:8080/api/hostinfo/'
58API_HOST_INFO_URL = API_HOST_INFO_BAD_URL + '127.0.0.1'
59
60API_SET_UPDATE_BAD_URL = 'http://127.0.0.1:8080/api/setnextupdate/'
61API_SET_UPDATE_URL = API_SET_UPDATE_BAD_URL + '127.0.0.1'
62
63API_SET_UPDATE_REQUEST = 'new_update-test/the-new-update'
64
Zdenek Behan1347a312011-02-10 03:59:17 +010065# Run all tests while being in /
66base_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
67os.chdir("/")
Girtsdba6ab22010-10-11 15:53:52 -070068
69class DevserverTest(unittest.TestCase):
70 """Regressions tests for devserver."""
71
72 def setUp(self):
73 """Copies in testing files."""
Girtsdba6ab22010-10-11 15:53:52 -070074
75 # Copy in developer-test.gz, as "static/" directory is hardcoded, and it
76 # would be very hard to change it (static file serving is handled deep
77 # inside webpy).
Zdenek Behan5d21a2a2011-02-12 02:06:01 +010078 self.image_src = os.path.join(base_dir, TEST_IMAGE)
79 self.image = os.path.join(base_dir, STATIC_DIR, TEST_IMAGE_NAME)
Girtsdba6ab22010-10-11 15:53:52 -070080 if os.path.exists(self.image):
81 os.unlink(self.image)
Zdenek Behan5d21a2a2011-02-12 02:06:01 +010082 shutil.copy(self.image_src, self.image)
Girtsdba6ab22010-10-11 15:53:52 -070083
84 self.factory_config = os.path.join(base_dir, TEST_FACTORY_CONFIG)
85
86 def tearDown(self):
87 """Removes testing files."""
88 if os.path.exists(self.image):
89 os.unlink(self.image)
90
Jay Srinivasanac69d262012-10-30 19:05:53 -070091 # Helper methods begin here.
Girtsdba6ab22010-10-11 15:53:52 -070092
Zdenek Behan5d21a2a2011-02-12 02:06:01 +010093 def _StartServer(self, data_dir=''):
Girtsdba6ab22010-10-11 15:53:52 -070094 """Starts devserver, returns process."""
95 cmd = [
96 'python',
Zdenek Behan1347a312011-02-10 03:59:17 +010097 os.path.join(base_dir, 'devserver.py'),
Girtsdba6ab22010-10-11 15:53:52 -070098 'devserver.py',
99 '--factory_config', self.factory_config,
100 ]
Zdenek Behan5d21a2a2011-02-12 02:06:01 +0100101 if data_dir:
102 cmd.append('--data_dir')
103 cmd.append(data_dir)
Girtsdba6ab22010-10-11 15:53:52 -0700104 process = subprocess.Popen(cmd)
105 return process.pid
106
Jay Srinivasanac69d262012-10-30 19:05:53 -0700107 def VerifyHandleUpdate(self, protocol, data_dir):
108 """Tests running the server and getting an update for the given protocol.
109 Takes an optional data_dir to pass to the devserver. """
110 pid = self._StartServer(data_dir)
Girtsdba6ab22010-10-11 15:53:52 -0700111 try:
112 # Wait for the server to start up.
113 time.sleep(1)
Jay Srinivasanac69d262012-10-30 19:05:53 -0700114 request = urllib2.Request(UPDATE_URL, UPDATE_REQUEST[protocol])
Girtsdba6ab22010-10-11 15:53:52 -0700115 connection = urllib2.urlopen(request)
116 response = connection.read()
Zdenek Behan5d21a2a2011-02-12 02:06:01 +0100117 connection.close()
Girtsdba6ab22010-10-11 15:53:52 -0700118 self.assertNotEqual('', response)
119
120 # Parse the response and check if it contains the right result.
121 dom = minidom.parseString(response)
122 update = dom.getElementsByTagName('updatecheck')[0]
Jay Srinivasanac69d262012-10-30 19:05:53 -0700123 if protocol == '2.0':
124 url = self.VerifyV2Response(update)
125 else:
126 url = self.VerifyV3Response(update)
Girtsdba6ab22010-10-11 15:53:52 -0700127
128 # Try to fetch the image.
Jay Srinivasanac69d262012-10-30 19:05:53 -0700129 connection = urllib2.urlopen(url)
Girtsdba6ab22010-10-11 15:53:52 -0700130 contents = connection.read()
Zdenek Behan5d21a2a2011-02-12 02:06:01 +0100131 connection.close()
Girtsdba6ab22010-10-11 15:53:52 -0700132 self.assertEqual('Developers, developers, developers!\n', contents)
133 finally:
134 os.kill(pid, signal.SIGKILL)
135
Jay Srinivasanac69d262012-10-30 19:05:53 -0700136 def VerifyV2Response(self, update):
137 """Verifies the update DOM from a v2 response and returns the url."""
138 codebase = update.getAttribute('codebase')
139 self.assertEqual(STATIC_URL + TEST_IMAGE_NAME,
140 codebase)
141
142 hash_value = update.getAttribute('hash')
143 self.assertEqual(EXPECTED_HASH, hash_value)
144
145 return codebase
146
147 def VerifyV3Response(self, update):
148 """Verifies the update DOM from a v3 response and returns the url."""
149 # Parse the response and check if it contains the right result.
150 urls = update.getElementsByTagName('urls')[0]
151 url = urls.getElementsByTagName('url')[0]
152
153 codebase = url.getAttribute('codebase')
154 self.assertEqual(STATIC_URL, codebase)
155
156 manifest = update.getElementsByTagName('manifest')[0]
157 packages = manifest.getElementsByTagName('packages')[0]
158 package = packages.getElementsByTagName('package')[0]
159 filename = package.getAttribute('name')
160 self.assertEqual(TEST_IMAGE_NAME, filename)
161
162 hash_value = package.getAttribute('hash')
163 self.assertEqual(EXPECTED_HASH, hash_value)
164
165 url = os.path.join(codebase, filename)
166 return url
167
168 def VerifyHandleDatadirUpdate(self, protocol):
Zdenek Behan5d21a2a2011-02-12 02:06:01 +0100169 """Tests getting an update from a specified datadir"""
170 # Push the image to the expected path where devserver picks it up.
171 image_path = os.path.join(TEST_DATA_PATH, STATIC_DIR)
172 if not os.path.exists(image_path):
173 os.makedirs(image_path)
174
175 foreign_image = os.path.join(image_path, TEST_IMAGE_NAME)
176 if os.path.exists(foreign_image):
177 os.unlink(foreign_image)
178 shutil.copy(self.image_src, foreign_image)
179
Jay Srinivasanac69d262012-10-30 19:05:53 -0700180 self.VerifyHandleUpdate(protocol, TEST_DATA_PATH)
181 os.unlink(foreign_image)
Zdenek Behan5d21a2a2011-02-12 02:06:01 +0100182
Jay Srinivasanac69d262012-10-30 19:05:53 -0700183 # Tests begin here.
Zdenek Behan5d21a2a2011-02-12 02:06:01 +0100184
Jay Srinivasanac69d262012-10-30 19:05:53 -0700185 def testValidateFactoryConfig(self):
186 """Tests --validate_factory_config."""
187 cmd = [
188 'python',
189 os.path.join(base_dir, 'devserver.py'),
190 '--validate_factory_config',
191 '--factory_config', self.factory_config,
192 ]
193 process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
194 stdout, _ = process.communicate()
195 self.assertEqual(0, process.returncode)
196 self.assertTrue('Config file looks good.' in stdout)
Zdenek Behan5d21a2a2011-02-12 02:06:01 +0100197
Jay Srinivasanac69d262012-10-30 19:05:53 -0700198 def testHandleUpdateV2(self):
199 self.VerifyHandleUpdate('2.0', '')
Zdenek Behan5d21a2a2011-02-12 02:06:01 +0100200
Jay Srinivasanac69d262012-10-30 19:05:53 -0700201 def testHandleUpdateV3(self):
202 self.VerifyHandleUpdate('3.0', '')
Zdenek Behan5d21a2a2011-02-12 02:06:01 +0100203
Jay Srinivasanac69d262012-10-30 19:05:53 -0700204 def testHandleDatadirUpdateV2(self):
205 self.VerifyHandleDatadirUpdate('2.0')
206
207 def testHandleDatadirUpdateV3(self):
208 self.VerifyHandleDatadirUpdate('3.0')
Zdenek Behan5d21a2a2011-02-12 02:06:01 +0100209
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700210 def testApiBadSetNextUpdateRequest(self):
211 """Tests sending a bad setnextupdate request."""
212 pid = self._StartServer()
213 try:
214 # Wait for the server to start up.
215 time.sleep(1)
216
217 # Send bad request and ensure it fails...
218 try:
219 request = urllib2.Request(API_SET_UPDATE_URL, '')
220 connection = urllib2.urlopen(request)
221 connection.read()
222 connection.close()
223 self.fail('Invalid setnextupdate request did not fail!')
224 except urllib2.URLError:
225 pass
226 finally:
227 os.kill(pid, signal.SIGKILL)
228
229 def testApiBadSetNextUpdateURL(self):
230 """Tests contacting a bad setnextupdate url."""
231 pid = self._StartServer()
232 try:
233 # Wait for the server to start up.
234 time.sleep(1)
235
236 # Send bad request and ensure it fails...
237 try:
238 connection = urllib2.urlopen(API_SET_UPDATE_BAD_URL)
239 connection.read()
240 connection.close()
241 self.fail('Invalid setnextupdate url did not fail!')
242 except urllib2.URLError:
243 pass
244 finally:
245 os.kill(pid, signal.SIGKILL)
246
247 def testApiBadHostInfoURL(self):
248 """Tests contacting a bad hostinfo url."""
249 pid = self._StartServer()
250 try:
251 # Wait for the server to start up.
252 time.sleep(1)
253
254 # Send bad request and ensure it fails...
255 try:
256 connection = urllib2.urlopen(API_HOST_INFO_BAD_URL)
257 connection.read()
258 connection.close()
259 self.fail('Invalid hostinfo url did not fail!')
260 except urllib2.URLError:
261 pass
262 finally:
263 os.kill(pid, signal.SIGKILL)
264
265 def testApiHostInfoAndSetNextUpdate(self):
266 """Tests using the setnextupdate and hostinfo api commands."""
267 pid = self._StartServer()
268 try:
269 # Wait for the server to start up.
270 time.sleep(1)
271
272 # Send setnextupdate command.
273 request = urllib2.Request(API_SET_UPDATE_URL, API_SET_UPDATE_REQUEST)
274 connection = urllib2.urlopen(request)
275 response = connection.read()
276 connection.close()
277
278 # Send hostinfo command and verify the setnextupdate worked.
279 connection = urllib2.urlopen(API_HOST_INFO_URL)
280 response = connection.read()
281 connection.close()
282
283 self.assertEqual(
284 json.loads(response)['forced_update_label'], API_SET_UPDATE_REQUEST)
285 finally:
286 os.kill(pid, signal.SIGKILL)
287
Girtsdba6ab22010-10-11 15:53:52 -0700288if __name__ == '__main__':
289 unittest.main()