1 # Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 """Playback driver.""" 5 6 import cgi 7 import simplejson as json 8 import os 9 import string 10 import sys 11 import threading 12 import urlparse 13 14 START_PAGE = """<html> 15 <script type="text/javascript"> 16 var runCount = $run_count; 17 var results = []; 18 19 function run() { 20 var wnd = window.open('?resource=start_page_popup', '', 21 'width=$width, height=$height'); 22 var timerId = setInterval(function() { 23 wnd.postMessage('ping', '$target_origin'); 24 }, 300); 25 var handleMessage = function(event) { 26 clearInterval(timerId); 27 wnd.close(); 28 document.writeln('<div>' + event.data + '</div>'); 29 results.push(event.data); 30 runCount -= 1; 31 window.removeEventListener('message', handleMessage); 32 if (runCount > 0) { 33 run(); 34 } else { 35 var xmlHttpRequest = new XMLHttpRequest(); 36 xmlHttpRequest.open("POST", '/benchmark/', true); 37 xmlHttpRequest.setRequestHeader("Content-type", "application/json"); 38 xmlHttpRequest.send(JSON.stringify({results: results})); 39 } 40 } 41 window.addEventListener('message', handleMessage, false); 42 } 43 44 run(); 45 </script> 46 </html> 47 """ 48 49 START_PAGE_POPUP = """<html> 50 <script type="text/javascript"> 51 window.setTimeout(function() { 52 console.log(window.innerWidth, window.innerHeight); 53 if (window.innerWidth == $width && window.innerHeight == $height) { 54 window.location = '$start_url'; 55 } else { 56 window.resizeBy($width - window.innerWidth, $height - window.innerHeight); 57 window.location = window.location; 58 } 59 }, 200); 60 </script> 61 </html> 62 """ 63 64 DATA_JS = 'Benchmark.data = $data;' 65 66 67 def ReadFile(file_name, mode='r'): 68 f = open(file_name, mode) 69 data = f.read() 70 f.close() 71 return data 72 73 74 def ReadJSON(file_name): 75 f = open(file_name, 'r') 76 data = json.load(f) 77 f.close() 78 return data 79 80 81 class PlaybackRequestHandler(object): 82 """This class is used to process HTTP requests during test playback. 83 84 Attributes: 85 test_dir: directory containing test files. 86 test_callback: function to be called when the test is finished. 87 script_dir: directory where javascript files are located. 88 """ 89 90 def __init__(self, test_dir, test_callback=None, script_dir=os.getcwd()): 91 self.test_dir = test_dir 92 self.test_callback = test_callback 93 self.script_dir = script_dir 94 95 def ProcessRequest(self, handler): 96 "Processes single HTTP request." 97 98 parse_result = urlparse.urlparse(handler.path) 99 if parse_result.path.endswith('/benchmark/'): 100 query = cgi.parse_qs(parse_result.query) 101 if 'run_test' in query: 102 run_count = 1 103 if 'run_count' in query: 104 run_count = query['run_count'][0] 105 self._StartTest(handler, self.test_dir, run_count) 106 elif 'resource' in query: 107 self._GetBenchmarkResource(query['resource'][0], handler) 108 else: 109 self._ProcessBenchmarkReport(handler.body, handler) 110 else: 111 self._GetApplicationResource(handler) 112 113 def _StartTest(self, handler, test_dir, run_count): 114 "Sends test start page to browser." 115 116 cache_data = ReadJSON(os.path.join(test_dir, 'cache.json')) 117 118 # Load cached responses. 119 self.cache = {} 120 responses_dir = os.path.join(test_dir, 'responses') 121 for request in cache_data['requests']: 122 response_file = os.path.join(responses_dir, request['response_file']) 123 response = ReadFile(response_file, 'rb') 124 key = (request['method'], request['path']) 125 self.cache[key] = {'response': response, 'headers': request['headers']} 126 127 # Load benchmark scripts. 128 self.benchmark_resources = {} 129 data = ReadFile(os.path.join(test_dir, 'data.json')) 130 data = string.Template(DATA_JS).substitute(data=data) 131 self.benchmark_resources['data.js'] = {'data': data, 132 'type': 'application/javascript'} 133 for resource in ('common.js', 'playback.js'): 134 resource_file = os.path.join(self.script_dir, resource) 135 self.benchmark_resources[resource] = {'data': ReadFile(resource_file), 136 'type': 'application/javascript'} 137 138 # Format start page. 139 parse_result = urlparse.urlparse(cache_data['start_url']) 140 target_origin = '%s://%s' % (parse_result.scheme, parse_result.netloc) 141 start_page = string.Template(START_PAGE).substitute( 142 run_count=run_count, target_origin=target_origin, 143 width=cache_data['width'], height=cache_data['height']) 144 self.benchmark_resources['start_page'] = { 145 'data': start_page, 146 'type': 'text/html; charset=UTF-8' 147 } 148 149 start_page_popup = string.Template(START_PAGE_POPUP).substitute( 150 start_url=cache_data['start_url'], 151 width=cache_data['width'], height=cache_data['height']) 152 self.benchmark_resources['start_page_popup'] = { 153 'data': start_page_popup, 154 'type': 'text/html; charset=UTF-8' 155 } 156 157 self._GetBenchmarkResource('start_page', handler) 158 159 def _GetBenchmarkResource(self, resource, handler): 160 "Sends requested resource to browser." 161 162 if resource in self.benchmark_resources: 163 resource = self.benchmark_resources[resource] 164 handler.send_response(200) 165 handler.send_header('content-length', len(resource['data'])) 166 handler.send_header('content-type', resource['type']) 167 handler.end_headers() 168 handler.wfile.write(resource['data']) 169 else: 170 handler.send_response(404) 171 handler.end_headers() 172 173 def _ProcessBenchmarkReport(self, content, handler): 174 "Reads benchmark score from report content and invokes callback." 175 176 handler.send_response(204) 177 handler.end_headers() 178 content = json.loads(content) 179 if 'results' in content: 180 results = content['results'] 181 sys.stdout.write('Results: %s\n' % results) 182 if self.test_callback: self.test_callback(results) 183 elif 'error' in content: 184 sys.stderr.write('Error: %s\n' % content['error']) 185 186 def _GetApplicationResource(self, handler): 187 "Searches for response in cache. If not found, responds with 204." 188 key = (handler.command, handler.path) 189 if key in self.cache: 190 sys.stdout.write('%s %s -> found\n' % key) 191 handler.wfile.write(self.cache[key]['response']) 192 else: 193 sys.stderr.write('%s %s -> not found\n' % key) 194 handler.send_response(204, "not in cache") 195 handler.end_headers() 196