blob: 44db7ed35542456069608c9044e4750be5b96f56 [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
Chris Sosa65d339b2013-01-21 18:59:21 -08009import json
Chris Sosa6a3697f2013-01-29 16:44:43 -080010from xml.dom import minidom
Girtsdba6ab22010-10-11 15:53:52 -070011import os
Chris Sosa855b8932013-08-21 13:24:55 -070012import psutil
Girtsdba6ab22010-10-11 15:53:52 -070013import shutil
Gilad Arnoldabb352e2012-09-23 01:24:27 -070014import signal
Girtsdba6ab22010-10-11 15:53:52 -070015import subprocess
Chris Sosa6a3697f2013-01-29 16:44:43 -080016import tempfile
Girtsdba6ab22010-10-11 15:53:52 -070017import time
18import unittest
19import urllib2
Gilad Arnoldabb352e2012-09-23 01:24:27 -070020
Girtsdba6ab22010-10-11 15:53:52 -070021
22# Paths are relative to this script's base directory.
joychen7c2054a2013-07-25 11:14:07 -070023LABEL = 'devserver'
Zdenek Behan5d21a2a2011-02-12 02:06:01 +010024TEST_IMAGE_PATH = 'testdata/devserver'
Chris Sosa6a3697f2013-01-29 16:44:43 -080025TEST_IMAGE_NAME = 'update.gz'
Zdenek Behan5d21a2a2011-02-12 02:06:01 +010026TEST_IMAGE = TEST_IMAGE_PATH + '/' + TEST_IMAGE_NAME
Jay Srinivasanac69d262012-10-30 19:05:53 -070027EXPECTED_HASH = 'kGcOinJ0vA8vdYX53FN0F5BdwfY='
Girtsdba6ab22010-10-11 15:53:52 -070028
Jay Srinivasanac69d262012-10-30 19:05:53 -070029# Update request based on Omaha v2 protocol format.
joychen121fc9b2013-08-02 14:30:30 -070030UPDATE_REQUEST = """<?xml version="1.0" encoding="UTF-8"?>
Jay Srinivasanac69d262012-10-30 19:05:53 -070031<request version="ChromeOSUpdateEngine-0.1.0.0" updaterversion="ChromeOSUpdateEngine-0.1.0.0" protocol="3.0" ismachine="1">
32 <os version="Indy" platform="Chrome OS" sp="0.11.254.2011_03_09_1814_i686"></os>
joychen121fc9b2013-08-02 14:30:30 -070033 <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">
Jay Srinivasanac69d262012-10-30 19:05:53 -070034 <updatecheck></updatecheck>
35 <event eventtype="3" eventresult="2" previousversion="0.11.216.2011_03_02_1358"></event>
36 </app>
37</request>
38"""
Chris Sosa6a3697f2013-01-29 16:44:43 -080039
Girtsdba6ab22010-10-11 15:53:52 -070040# TODO(girts): use a random available port.
joychen7c2054a2013-07-25 11:14:07 -070041UPDATE_URL = 'http://127.0.0.1:8080/update/devserver'
joychen3b6bac62013-07-12 11:42:49 -070042CHECK_HEALTH_URL = 'http://127.0.0.1:8080/check_health'
joychened64b222013-06-21 16:39:34 -070043STATIC_URL = 'http://127.0.0.1:8080/static/'
joychen7c2054a2013-07-25 11:14:07 -070044SERVE_URL = STATIC_URL + LABEL + '/'
Girtsdba6ab22010-10-11 15:53:52 -070045
Dale Curtisc9aaf3a2011-08-09 15:47:40 -070046API_HOST_INFO_BAD_URL = 'http://127.0.0.1:8080/api/hostinfo/'
47API_HOST_INFO_URL = API_HOST_INFO_BAD_URL + '127.0.0.1'
48
49API_SET_UPDATE_BAD_URL = 'http://127.0.0.1:8080/api/setnextupdate/'
50API_SET_UPDATE_URL = API_SET_UPDATE_BAD_URL + '127.0.0.1'
51
52API_SET_UPDATE_REQUEST = 'new_update-test/the-new-update'
53
joychen3b6bac62013-07-12 11:42:49 -070054DEVSERVER_START_TIMEOUT = 15
Girtsdba6ab22010-10-11 15:53:52 -070055
56class DevserverTest(unittest.TestCase):
57 """Regressions tests for devserver."""
58
59 def setUp(self):
60 """Copies in testing files."""
Girtsdba6ab22010-10-11 15:53:52 -070061
62 # Copy in developer-test.gz, as "static/" directory is hardcoded, and it
63 # would be very hard to change it (static file serving is handled deep
64 # inside webpy).
Chris Sosa6a3697f2013-01-29 16:44:43 -080065 self.test_data_path = tempfile.mkdtemp()
66 self.src_dir = os.path.dirname(__file__)
joychen7c2054a2013-07-25 11:14:07 -070067
68 # Copy the payload to the root of static_dir.
Chris Sosa6a3697f2013-01-29 16:44:43 -080069 self.image_src = os.path.join(self.src_dir, TEST_IMAGE)
70 self.image = os.path.join(self.test_data_path, TEST_IMAGE_NAME)
Zdenek Behan5d21a2a2011-02-12 02:06:01 +010071 shutil.copy(self.image_src, self.image)
joychen7c2054a2013-07-25 11:14:07 -070072
73 # Copy the payload to the location of the update label "devserver."
74 os.makedirs(os.path.join(self.test_data_path, LABEL))
75 shutil.copy(self.image_src, os.path.join(self.test_data_path, LABEL,
76 TEST_IMAGE_NAME))
77
78 # Copy the payload to the location of forced label.
79 os.makedirs(os.path.join(self.test_data_path, API_SET_UPDATE_REQUEST))
80 shutil.copy(self.image_src, os.path.join(self.test_data_path,
81 API_SET_UPDATE_REQUEST,
82 TEST_IMAGE_NAME))
83
Chris Sosa855b8932013-08-21 13:24:55 -070084 self.pidfile = tempfile.mktemp('devserver_unittest')
85 self.devserver_process = self._StartServer(self.pidfile)
Girtsdba6ab22010-10-11 15:53:52 -070086
Girtsdba6ab22010-10-11 15:53:52 -070087 def tearDown(self):
88 """Removes testing files."""
Chris Sosa6a3697f2013-01-29 16:44:43 -080089 shutil.rmtree(self.test_data_path)
joychen3b6bac62013-07-12 11:42:49 -070090 os.kill(self.devserver_process.pid, signal.SIGKILL)
Girtsdba6ab22010-10-11 15:53:52 -070091
Jay Srinivasanac69d262012-10-30 19:05:53 -070092 # Helper methods begin here.
Girtsdba6ab22010-10-11 15:53:52 -070093
Chris Sosa855b8932013-08-21 13:24:55 -070094 def _StartServer(self, pidfile):
Girtsdba6ab22010-10-11 15:53:52 -070095 """Starts devserver, returns process."""
96 cmd = [
97 'python',
Chris Sosa6a3697f2013-01-29 16:44:43 -080098 os.path.join(self.src_dir, 'devserver.py'),
Girtsdba6ab22010-10-11 15:53:52 -070099 'devserver.py',
Chris Sosa855b8932013-08-21 13:24:55 -0700100 '--static_dir', self.test_data_path,
101 '--pidfile', pidfile
Chris Sosa6a3697f2013-01-29 16:44:43 -0800102 ]
Chris Sosa855b8932013-08-21 13:24:55 -0700103 if pidfile:
104 cmd.extend(['--pidfile', pidfile])
Chris Sosa6a3697f2013-01-29 16:44:43 -0800105
joychen3b6bac62013-07-12 11:42:49 -0700106 process = subprocess.Popen(cmd,
107 stderr=subprocess.PIPE)
108
109 # wait for devserver to start
110 current_time = time.time()
111 deadline = current_time + DEVSERVER_START_TIMEOUT
112 while current_time < deadline:
113 current_time = time.time()
114 try:
115 urllib2.urlopen(CHECK_HEALTH_URL, timeout=0.05)
116 break
117 except Exception:
118 continue
119 else:
120 self.fail('Devserver failed to start within timeout.')
121
122 return process
Girtsdba6ab22010-10-11 15:53:52 -0700123
joychen121fc9b2013-08-02 14:30:30 -0700124 def VerifyHandleUpdate(self):
Chris Sosa6a3697f2013-01-29 16:44:43 -0800125 """Tests running the server and getting an update for the given protocol."""
joychen121fc9b2013-08-02 14:30:30 -0700126 request = urllib2.Request(UPDATE_URL, UPDATE_REQUEST)
joychen3b6bac62013-07-12 11:42:49 -0700127 connection = urllib2.urlopen(request)
128 response = connection.read()
129 connection.close()
130 self.assertNotEqual('', response)
Girtsdba6ab22010-10-11 15:53:52 -0700131
joychen3b6bac62013-07-12 11:42:49 -0700132 # Parse the response and check if it contains the right result.
133 dom = minidom.parseString(response)
134 update = dom.getElementsByTagName('updatecheck')[0]
joychen121fc9b2013-08-02 14:30:30 -0700135 url = self.VerifyV3Response(update)
Girtsdba6ab22010-10-11 15:53:52 -0700136
joychen3b6bac62013-07-12 11:42:49 -0700137 # Try to fetch the image.
138 connection = urllib2.urlopen(url)
139 contents = connection.read()
140 connection.close()
141 self.assertEqual('Developers, developers, developers!\n', contents)
Girtsdba6ab22010-10-11 15:53:52 -0700142
Jay Srinivasanac69d262012-10-30 19:05:53 -0700143 def VerifyV3Response(self, update):
144 """Verifies the update DOM from a v3 response and returns the url."""
145 # Parse the response and check if it contains the right result.
146 urls = update.getElementsByTagName('urls')[0]
147 url = urls.getElementsByTagName('url')[0]
148
149 codebase = url.getAttribute('codebase')
joychen7c2054a2013-07-25 11:14:07 -0700150 self.assertEqual(SERVE_URL, codebase)
Jay Srinivasanac69d262012-10-30 19:05:53 -0700151
152 manifest = update.getElementsByTagName('manifest')[0]
153 packages = manifest.getElementsByTagName('packages')[0]
154 package = packages.getElementsByTagName('package')[0]
155 filename = package.getAttribute('name')
156 self.assertEqual(TEST_IMAGE_NAME, filename)
157
158 hash_value = package.getAttribute('hash')
159 self.assertEqual(EXPECTED_HASH, hash_value)
160
161 url = os.path.join(codebase, filename)
162 return url
163
Jay Srinivasanac69d262012-10-30 19:05:53 -0700164 # Tests begin here.
Jay Srinivasanac69d262012-10-30 19:05:53 -0700165 def testHandleUpdateV3(self):
joychen121fc9b2013-08-02 14:30:30 -0700166 self.VerifyHandleUpdate()
Zdenek Behan5d21a2a2011-02-12 02:06:01 +0100167
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700168 def testApiBadSetNextUpdateRequest(self):
169 """Tests sending a bad setnextupdate request."""
joychen3b6bac62013-07-12 11:42:49 -0700170 # Send bad request and ensure it fails...
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700171 try:
joychen3b6bac62013-07-12 11:42:49 -0700172 request = urllib2.Request(API_SET_UPDATE_URL, '')
173 connection = urllib2.urlopen(request)
174 connection.read()
175 connection.close()
176 self.fail('Invalid setnextupdate request did not fail!')
177 except urllib2.URLError:
178 pass
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700179
180 def testApiBadSetNextUpdateURL(self):
181 """Tests contacting a bad setnextupdate url."""
joychen3b6bac62013-07-12 11:42:49 -0700182 # Send bad request and ensure it fails...
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700183 try:
joychen3b6bac62013-07-12 11:42:49 -0700184 connection = urllib2.urlopen(API_SET_UPDATE_BAD_URL)
185 connection.read()
186 connection.close()
187 self.fail('Invalid setnextupdate url did not fail!')
188 except urllib2.URLError:
189 pass
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700190
191 def testApiBadHostInfoURL(self):
192 """Tests contacting a bad hostinfo url."""
joychen3b6bac62013-07-12 11:42:49 -0700193 # Send bad request and ensure it fails...
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700194 try:
joychen3b6bac62013-07-12 11:42:49 -0700195 connection = urllib2.urlopen(API_HOST_INFO_BAD_URL)
196 connection.read()
197 connection.close()
198 self.fail('Invalid hostinfo url did not fail!')
199 except urllib2.URLError:
200 pass
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700201
202 def testApiHostInfoAndSetNextUpdate(self):
203 """Tests using the setnextupdate and hostinfo api commands."""
joychen3b6bac62013-07-12 11:42:49 -0700204 # Send setnextupdate command.
205 request = urllib2.Request(API_SET_UPDATE_URL, API_SET_UPDATE_REQUEST)
206 connection = urllib2.urlopen(request)
207 response = connection.read()
208 connection.close()
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700209
joychen3b6bac62013-07-12 11:42:49 -0700210 # Send hostinfo command and verify the setnextupdate worked.
211 connection = urllib2.urlopen(API_HOST_INFO_URL)
212 response = connection.read()
213 connection.close()
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700214
joychen3b6bac62013-07-12 11:42:49 -0700215 self.assertEqual(
216 json.loads(response)['forced_update_label'], API_SET_UPDATE_REQUEST)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700217
Chris Sosa855b8932013-08-21 13:24:55 -0700218 def testPidFile(self):
219 """Test that using a pidfile works correctly."""
220 with open(self.pidfile, 'r') as f:
221 pid = f.read()
222
223 # Let's assert some process information about the devserver.
224 self.assertTrue(pid.isdigit())
225 process = psutil.Process(int(pid))
226 self.assertTrue(process.is_running())
227 self.assertTrue('devserver.py' in process.cmdline)
228
Chris Sosa6a3697f2013-01-29 16:44:43 -0800229
Girtsdba6ab22010-10-11 15:53:52 -0700230if __name__ == '__main__':
231 unittest.main()