OLD | NEW |
| (Empty) |
1 # Copyright (c) 2012 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 | |
5 """Constrained network server (CNS) test base.""" | |
6 | |
7 import logging | |
8 import os | |
9 import Queue | |
10 import subprocess | |
11 import sys | |
12 import threading | |
13 import urllib2 | |
14 | |
15 import pyauto | |
16 import pyauto_paths | |
17 | |
18 | |
19 # List of commonly used network constraints settings. | |
20 # Each setting is a tuppe of the form: | |
21 # ('TEST_NAME', [BANDWIDTH_Kbps, LATENCY_ms, PACKET_LOSS_%]) | |
22 # | |
23 # Note: The test name should satisfy the regex [\w\.-]+ (check | |
24 # tools/perf_expectations/tests/perf_expectations_unittest.py for details). It | |
25 # is used to name the result graphs on the dashboards. | |
26 # | |
27 # The WiFi, DSL, and Cable settings were taken from webpagetest.org as | |
28 # approximations of their respective real world networks. The settings were | |
29 # based on 2011 FCC Broadband Data report (http://www.fcc.gov/document/ | |
30 # measuring-broadband-america-report-consumer-broadband-performance-us). | |
31 DialUp = ('DialUp', [56, 120, 5]) | |
32 Slow = ('Slow', [256, 105, 1]) | |
33 Wifi = ('Wifi', [1024, 60, 0]) | |
34 DSL = ('DSL', [1541, 50, 0]) | |
35 Cable = ('Cable', [5120, 28, 0]) | |
36 NoConstraints = ('NoConstraints', [0, 0, 0]) | |
37 | |
38 # Path to CNS executable relative to source root. | |
39 _CNS_PATH = os.path.join( | |
40 'media', 'tools', 'constrained_network_server', 'cns.py') | |
41 | |
42 # Port to start the CNS on. | |
43 _CNS_PORT = 9000 | |
44 | |
45 # A flag to determine whether to launch a local CNS instance or to connect | |
46 # to the external CNS server. Default to False since all current bots use an | |
47 # external instance. | |
48 # If not on Windows, set USE_LOCAL_CNS=1 env variable to switch the flag. | |
49 USE_LOCAL_CNS = ('win' not in sys.platform and 'USE_LOCAL_CNS' in os.environ and | |
50 os.environ['USE_LOCAL_CNS'] == '1') | |
51 | |
52 # Base CNS URL, only requires & separated parameter names appended. | |
53 if USE_LOCAL_CNS: | |
54 CNS_BASE_URL = 'http://127.0.0.1:%d/ServeConstrained?' % _CNS_PORT | |
55 else: | |
56 CNS_BASE_URL = 'http://chromeperf34:%d/ServeConstrained?' % _CNS_PORT | |
57 CNS_CLEANUP_URL = 'http://chromeperf34:%d/Cleanup' % _CNS_PORT | |
58 | |
59 # Used for server sanity check. | |
60 _TEST_VIDEO = 'roller.webm' | |
61 | |
62 # Directory root to serve files from. | |
63 _ROOT_PATH = os.path.join(pyauto.PyUITest.DataDir(), 'pyauto_private', 'media') | |
64 | |
65 | |
66 class CNSTestBase(pyauto.PyUITest): | |
67 """CNS test base hadles startup and teardown of CNS server.""" | |
68 | |
69 def __init__(self, *args, **kwargs): | |
70 """Initialize CNSTestBase by setting the arguments for CNS server. | |
71 | |
72 Args: | |
73 Check cns.py command line argument list for details. | |
74 """ | |
75 self._port = kwargs.get('port', _CNS_PORT) | |
76 self._interface = kwargs.get('interface', 'lo') | |
77 self._www_root = kwargs.get('www_root', _ROOT_PATH) | |
78 self._verbose = kwargs.get('verbose', True) | |
79 self._expiry_time = kwargs.get('expiry_time', 0) | |
80 self._socket_timeout = kwargs.get('socket_timeout') | |
81 pyauto.PyUITest.__init__(self, *args, **kwargs) | |
82 | |
83 def setUp(self): | |
84 """Ensures the Constrained Network Server (CNS) server is up and running.""" | |
85 if USE_LOCAL_CNS: | |
86 self._SetUpLocal() | |
87 else: | |
88 self._SetUpExternal() | |
89 | |
90 def _SetUpExternal(self): | |
91 """Ensures the test can connect to the external CNS server.""" | |
92 if self.WaitUntil(self._CanAccessServer, retry_sleep=3, timeout=30, | |
93 debug=False): | |
94 pyauto.PyUITest.setUp(self) | |
95 else: | |
96 self.fail('Failed to connect to CNS.') | |
97 | |
98 def _SetUpLocal(self): | |
99 """Starts the CNS server locally.""" | |
100 cmd = [sys.executable, os.path.join(pyauto_paths.GetSourceDir(), _CNS_PATH), | |
101 '--port', str(self._port), | |
102 '--interface', self._interface, | |
103 '--www-root', self._www_root, | |
104 '--expiry-time', str(self._expiry_time)] | |
105 | |
106 if self._socket_timeout: | |
107 cmd.extend(['--socket-timeout', str(self._socket_timeout)]) | |
108 if self._verbose: | |
109 cmd.append('-v') | |
110 logging.debug('Starting CNS server: %s ', ' '.join(cmd)) | |
111 | |
112 self._cns_process = subprocess.Popen(cmd, stderr=subprocess.PIPE) | |
113 ProcessLogger(self._cns_process) | |
114 | |
115 if self.WaitUntil(self._CanAccessServer, retry_sleep=3, timeout=30, | |
116 debug=False): | |
117 pyauto.PyUITest.setUp(self) | |
118 else: | |
119 self.tearDown() | |
120 self.fail('Failed to start CNS.') | |
121 | |
122 def _CanAccessServer(self): | |
123 """Checks if the CNS server can serve a file with no network constraints.""" | |
124 test_url = ''.join([CNS_BASE_URL, 'f=', _TEST_VIDEO]) | |
125 try: | |
126 return urllib2.urlopen(test_url) is not None | |
127 except Exception: | |
128 return False | |
129 | |
130 def tearDown(self): | |
131 """Stops the Constrained Network Server (CNS).""" | |
132 if USE_LOCAL_CNS: | |
133 logging.debug('Stopping CNS server.') | |
134 # Do not use process.kill(), it will not clean up cns. | |
135 self.Kill(self._cns_process.pid) | |
136 # Need to wait since the process logger has a lock on the process stderr. | |
137 self._cns_process.wait() | |
138 self.assertFalse(self._cns_process.returncode is None) | |
139 logging.debug('CNS server stopped.') | |
140 else: | |
141 # Call CNS Cleanup to remove all ports created by this client. | |
142 self.NavigateToURL(CNS_CLEANUP_URL) | |
143 pyauto.PyUITest.tearDown(self) | |
144 | |
145 | |
146 class ProcessLogger(threading.Thread): | |
147 """A thread to log a process's stderr output.""" | |
148 | |
149 def __init__(self, process): | |
150 """Starts the process logger thread. | |
151 | |
152 Args: | |
153 process: The process to log. | |
154 """ | |
155 threading.Thread.__init__(self) | |
156 self._process = process | |
157 self.start() | |
158 | |
159 def run(self): | |
160 """Adds debug statements for the process's stderr output.""" | |
161 line = True | |
162 while line: | |
163 line = self._process.stderr.readline() | |
164 logging.debug(line.strip()) | |
165 | |
166 | |
167 def GetFileURL(file_name, bandwidth=0, latency=0, loss=0, new_port=False): | |
168 """Returns CNS URL for the file with specified constraints. | |
169 | |
170 Args: | |
171 Check cns.ServeConstrained() args for more details. | |
172 """ | |
173 video_url = [CNS_BASE_URL, 'f=' + file_name] | |
174 if bandwidth > 0: | |
175 video_url.append('bandwidth=%d' % bandwidth) | |
176 if latency > 0: | |
177 video_url.append('latency=%d' % latency) | |
178 if loss > 0: | |
179 video_url.append('loss=%d' % loss) | |
180 if new_port: | |
181 video_url.append('new_port=%s' % new_port) | |
182 return '&'.join(video_url) | |
183 | |
184 | |
185 def CreateCNSPerfTasks(network_constraints_settings, test_media_files): | |
186 """Returns a queue of tasks combinining network constrains with media files. | |
187 | |
188 Args: | |
189 network_constraints_settings: List of (setting_name, setting_values) | |
190 tupples. | |
191 test_media_files: List of media files to run the tests on. | |
192 """ | |
193 # Convert relative test path into an absolute path. | |
194 tasks = Queue.Queue() | |
195 for file_name in test_media_files: | |
196 for series_name, settings in network_constraints_settings: | |
197 logging.debug('Add test: %s\tSettings: %s\tMedia: %s', series_name, | |
198 settings, file_name) | |
199 tasks.put((series_name, settings, file_name)) | |
200 | |
201 return tasks | |
OLD | NEW |