OLD | NEW |
---|---|
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 import copy | 6 import copy |
7 import ctypes | 7 import ctypes |
8 from distutils import version | 8 from distutils import version |
9 import fnmatch | 9 import fnmatch |
10 import glob | 10 import glob |
11 import hashlib | 11 import hashlib |
12 import logging | 12 import logging |
13 import os | 13 import os |
14 import platform | 14 import platform |
15 import re | |
15 import shutil | 16 import shutil |
16 import subprocess | 17 import subprocess |
17 import sys | 18 import sys |
18 import tarfile | |
19 import tempfile | 19 import tempfile |
20 import urllib2 | 20 import urllib2 |
21 import xml.dom.minidom | 21 import xml.dom.minidom |
22 import zipfile | |
22 | 23 |
23 import pyauto_functional # Must be imported before pyauto. | 24 import pyauto_functional # Must be imported before pyauto. |
24 import pyauto | 25 import pyauto |
25 import pyauto_utils | 26 import pyauto_utils |
26 | 27 |
27 | 28 |
28 class NaClSDKTest(pyauto.PyUITest): | 29 class NaClSDKTest(pyauto.PyUITest): |
29 """Tests for the NaCl SDK.""" | 30 """Tests for the NaCl SDK.""" |
30 _extracted_sdk_path = None | 31 _extracted_sdk_path = None |
31 _temp_dir = None | 32 _temp_dir = None |
33 _pepper_versions = [] | |
34 _updated_pepper_versions = [] | |
35 _latest_updated_pepper_versions = [] | |
32 _settings = { | 36 _settings = { |
33 'release_win_sdk_url': 'http://commondatastorage.googleapis.com/' | |
34 'nativeclient-mirror/nacl/nacl_sdk/staging/naclsdk_win.exe', | |
35 'release_mac_sdk_url': 'http://commondatastorage.googleapis.com/' | |
36 'nativeclient-mirror/nacl/nacl_sdk/staging/naclsdk_mac.tgz', | |
37 'release_lin_sdk_url': 'http://commondatastorage.googleapis.com/' | |
38 'nativeclient-mirror/nacl/nacl_sdk/staging/naclsdk_linux.tgz', | |
39 'expected_md5_url': 'http://commondatastorage.googleapis.com/' | |
40 'nativeclient-mirror', | |
41 'release_win_expected_md5_key': 'nacl/nacl_sdk/staging/naclsdk_win.exe', | |
42 'release_mac_expected_md5_key': 'nacl/nacl_sdk/staging/naclsdk_mac.tgz', | |
43 'release_lin_expected_md5_key': 'nacl/nacl_sdk/staging/' | |
44 'naclsdk_linux.tgz', | |
45 'post_sdk_download_url': 'http://code.google.com/chrome/nativeclient/' | 37 'post_sdk_download_url': 'http://code.google.com/chrome/nativeclient/' |
46 'docs/download.html', | 38 'docs/download.html', |
47 'post_win_sdk_url': 'http://commondatastorage.googleapis.com/' | 39 'post_sdk_zip': 'http://commondatastorage.googleapis.com/' |
48 'nativeclient-mirror/nacl/nacl_sdk/naclsdk_win.exe', | 40 'nativeclient-mirror/nacl/nacl_sdk/nacl_sdk.zip', |
49 'post_mac_sdk_url': 'http://commondatastorage.googleapis.com/' | |
50 'nativeclient-mirror/nacl/nacl_sdk/naclsdk_mac.tgz', | |
51 'post_lin_sdk_url': 'http://commondatastorage.googleapis.com/' | |
52 'nativeclient-mirror/nacl/nacl_sdk/naclsdk_linux.tgz', | |
53 'min_required_chrome_build': 14, | 41 'min_required_chrome_build': 14, |
54 'gallery_examples': { | |
55 'life': 'http://nacl-gallery.appspot.com/life/life.html', | |
56 'hello_world': 'http://nacl-gallery.appspot.com/hello_world/' | |
57 'hello_world.html', | |
58 'pi_generator': 'http://nacl-gallery.appspot.com/pi_generator/' | |
59 'pi_generator.html', | |
60 'sine_synth': 'http://nacl-gallery.appspot.com/sine_synth/' | |
61 'sine_synth.html' | |
62 }, | |
63 'prerelease_gallery': { | |
64 'hello_world': 'http://4.nacl-gallery.appspot.com/hello_world/' | |
65 'hello_world.html', | |
66 'hello_world_c': 'http://4.nacl-gallery.appspot.com/hello_world_c/' | |
67 'hello_world.html', | |
68 'life': 'http://4.nacl-gallery.appspot.com/life/life.html', | |
69 'pi_generator': 'http://4.nacl-gallery.appspot.com/pi_generator/' | |
70 'pi_generator.html', | |
71 'sine_synth': 'http://4.nacl-gallery.appspot.com/sine_synth/' | |
72 'sine_synth.html', | |
73 'geturl': 'http://4.nacl-gallery.appspot.com/geturl/geturl.html' | |
74 } | |
75 } | 42 } |
76 | 43 |
77 def tearDown(self): | 44 def tearDown(self): |
78 pyauto.PyUITest.tearDown(self) | 45 pyauto.PyUITest.tearDown(self) |
79 self._RemoveDownloadedTestFile() | 46 self._RemoveDownloadedTestFile() |
80 | 47 |
81 def testNaClSDK(self): | 48 def testNaClSDK(self): |
82 """Verify that NaCl SDK is working properly.""" | 49 """Verify that NaCl SDK is working properly.""" |
83 if not self._HasAllSystemRequirements(): | 50 if not self._HasAllSystemRequirements(): |
84 logging.info('System does not meet the requirements.') | 51 logging.info('System does not meet the requirements.') |
85 return | 52 return |
86 self._extracted_sdk_path = tempfile.mkdtemp() | 53 self._extracted_sdk_path = tempfile.mkdtemp() |
87 | |
88 self._VerifyDownloadLinks() | 54 self._VerifyDownloadLinks() |
89 self._VerifyNaClSDKInstaller() | 55 self._VerifyNaClSDKInstaller() |
90 self._VerifyBuildStubProject() | 56 self._VerifyInstall() |
57 self._VerifyUpdate() | |
91 self._LaunchServerAndVerifyExamples() | 58 self._LaunchServerAndVerifyExamples() |
92 self._VerifyRebuildExamples() | |
93 self._VerifySelldrAndNcval() | |
94 | |
95 def testVerifyNaClSDKChecksum(self): | |
96 """Verify NaCl SDK Checksum.""" | |
97 if not self._HasAllSystemRequirements(): | |
98 logging.info('System does not meet the requirements.') | |
99 return | |
100 | |
101 self._DownloadNaClSDK() | |
102 | |
103 if pyauto.PyUITest.IsWin(): | |
104 expected_md5_key = self._settings['release_win_expected_md5_key'] | |
105 file_path = os.path.join(self._temp_dir, 'naclsdk_win.exe') | |
106 elif pyauto.PyUITest.IsMac(): | |
107 expected_md5_key = self._settings['release_mac_expected_md5_key'] | |
108 file_path = os.path.join(self._temp_dir, 'naclsdk_mac.tgz') | |
109 elif pyauto.PyUITest.IsLinux(): | |
110 expected_md5_key = self._settings['release_lin_expected_md5_key'] | |
111 file_path = os.path.join(self._temp_dir, 'naclsdk_linux.tgz') | |
112 else: | |
113 self.fail(msg='NaCl SDK does not support this OS.') | |
114 | |
115 # Get expected MD5. | |
116 expected_md5_url = self._settings['expected_md5_url'] | |
117 response = urllib2.urlopen(expected_md5_url) | |
118 dom = xml.dom.minidom.parseString(response.read()) | |
119 dom_content = dom.getElementsByTagName('Contents') | |
120 expected_md5 = None | |
121 for con in dom_content: | |
122 if (self._GetXMLNodeData(con.getElementsByTagName('Key')[0].childNodes) | |
123 == expected_md5_key): | |
124 node = con.getElementsByTagName('ETag')[0].childNodes | |
125 expected_md5 = self._GetXMLNodeData(node).strip('"') | |
126 self.assertTrue(expected_md5, | |
127 msg='Cannot get expected MD5 from %s.' % expected_md5_url) | |
128 | |
129 md5 = hashlib.md5() | |
130 md5.update(open(file_path).read()) | |
131 md5_sum = md5.hexdigest() | |
132 self.assertEqual(expected_md5, md5_sum, | |
133 msg='Unexpected checksum. Expected: %s, got: %s' | |
134 % (expected_md5, md5_sum)) | |
135 | |
136 def testVerifyNaClPlugin(self): | |
137 """Verify NaCl plugin.""" | |
138 if not self._HasAllSystemRequirements(): | |
139 logging.info('System does not meet the requirements.') | |
140 return | |
141 self._OpenExamplesAndStartTest( | |
142 self._settings['gallery_examples']) | |
143 | |
144 def testVerifyPrereleaseGallery(self): | |
145 """Verify Pre-release gallery examples.""" | |
146 if not self._HasAllSystemRequirements(): | |
147 logging.info('System does not meet the requirements.') | |
148 return | |
149 self._OpenExamplesAndStartTest( | |
150 self._settings['prerelease_gallery']) | |
151 | 59 |
152 def _VerifyDownloadLinks(self): | 60 def _VerifyDownloadLinks(self): |
153 """Verify the download links.""" | 61 """Verify the download links. |
62 | |
63 Simply verify that NaCl download links exist in html page. | |
64 """ | |
154 html = None | 65 html = None |
155 for i in xrange(3): | 66 for i in xrange(3): |
156 try: | 67 try: |
157 html = urllib2.urlopen(self._settings['post_sdk_download_url']).read() | 68 html = urllib2.urlopen(self._settings['post_sdk_download_url']).read() |
158 break | 69 break |
159 except: | 70 except: |
160 pass | 71 pass |
161 self.assertTrue(html, | 72 self.assertTrue(html, |
162 msg='Cannot open URL: %s' % | 73 msg='Cannot open URL: %s' % |
163 self._settings['post_sdk_download_url']) | 74 self._settings['post_sdk_download_url']) |
164 | 75 sdk_url = self._settings['post_sdk_zip'] |
165 # Make sure the correct URL is under the correct label. | 76 self.assertTrue(sdk_url in html, |
166 if pyauto.PyUITest.IsWin(): | 77 msg='Missing SDK download URL: %s' % sdk_url) |
167 win_sdk_url = self._settings['post_win_sdk_url'] | |
168 win_url_index = html.find(win_sdk_url) | |
169 self.assertTrue(win_url_index > -1, | |
170 msg='Missing SDK download URL: %s' % win_sdk_url) | |
171 win_keyword_index = html.rfind('Windows', 0, win_url_index) | |
172 self.assertTrue(win_keyword_index > -1, | |
173 msg='Misplaced download link: %s' % win_sdk_url) | |
174 elif pyauto.PyUITest.IsMac(): | |
175 mac_sdk_url = self._settings['post_mac_sdk_url'] | |
176 mac_url_index = html.find(mac_sdk_url) | |
177 self.assertTrue(mac_url_index > -1, | |
178 msg='Missing SDK download URL: %s' % mac_sdk_url) | |
179 mac_keyword_index = html.rfind('Macintosh', 0, mac_url_index) | |
180 self.assertTrue(mac_keyword_index > -1, | |
181 msg='Misplaced download link: %s' % mac_sdk_url) | |
182 elif pyauto.PyUITest.IsLinux(): | |
183 lin_sdk_url = self._settings['post_lin_sdk_url'] | |
184 lin_url_index = html.find(lin_sdk_url) | |
185 self.assertTrue(lin_url_index > -1, | |
186 msg='Missing SDK download URL: %s' % lin_sdk_url) | |
187 lin_keyword_index = html.rfind('Linux', 0, lin_url_index) | |
188 self.assertTrue(lin_keyword_index > -1, | |
189 msg='Misplaced download link: %s' % lin_sdk_url) | |
190 else: | |
191 self.fail(msg='NaCl SDK does not support this OS.') | |
192 | 78 |
193 def _VerifyNaClSDKInstaller(self): | 79 def _VerifyNaClSDKInstaller(self): |
194 """Verify NaCl SDK installer.""" | 80 """Verify NaCl SDK installer.""" |
195 search_list = [ | 81 search_list = [ |
196 'build.scons', | 82 'sdk_cache/', |
197 'favicon.ico', | 83 'sdk_tools/', |
198 'geturl/', | |
199 'hello_world/', | |
200 'hello_world_c/', | |
201 'httpd.py', | |
202 'index.html', | |
203 'nacl_sdk_scons/', | |
204 'pi_generator/', | |
205 'scons', | |
206 'sine_synth/' | |
207 ] | 84 ] |
208 | |
209 mac_lin_additional_search_items = [ | 85 mac_lin_additional_search_items = [ |
210 'sel_ldr_x86_32', | 86 'naclsdk', |
211 'sel_ldr_x86_64', | |
212 'ncval_x86_32', | |
213 'ncval_x86_64' | |
214 ] | 87 ] |
215 | |
216 win_additional_search_items = [ | 88 win_additional_search_items = [ |
217 'httpd.cmd', | 89 'naclsdk.bat' |
218 'sel_ldr_x86_32.exe', | |
219 'sel_ldr_x86_64.exe', | |
220 'ncval_x86_32.exe', | |
221 'ncval_x86_64.exe' | |
222 ] | 90 ] |
223 | |
224 self._DownloadNaClSDK() | 91 self._DownloadNaClSDK() |
225 | 92 self._ExtractNaClSDK() |
226 if pyauto.PyUITest.IsWin(): | 93 if pyauto.PyUITest.IsWin(): |
227 self._ExtractNaClSDK() | 94 self._SearchNaClSDKFile( |
228 self._SearchNaClSDKFileWindows( | |
229 search_list + win_additional_search_items) | 95 search_list + win_additional_search_items) |
230 elif pyauto.PyUITest.IsMac(): | 96 elif pyauto.PyUITest.IsMac() or pyauto.PyUITest.IsLinux(): |
231 source_file = os.path.join(self._temp_dir, 'naclsdk_mac.tgz') | 97 self._SearchNaClSDKFile( |
232 self._SearchNaClSDKTarFile(search_list + mac_lin_additional_search_items, | 98 search_list + mac_lin_additional_search_items) |
233 source_file) | |
234 self._ExtractNaClSDK() | |
235 elif pyauto.PyUITest.IsLinux(): | |
236 source_file = os.path.join(self._temp_dir, 'naclsdk_linux.tgz') | |
237 self._SearchNaClSDKTarFile(search_list + mac_lin_additional_search_items, | |
238 source_file) | |
239 self._ExtractNaClSDK() | |
240 else: | 99 else: |
241 self.fail(msg='NaCl SDK does not support this OS.') | 100 self.fail(msg='NaCl SDK does not support this OS.') |
242 | 101 |
243 def _VerifyBuildStubProject(self): | 102 def _VerifyInstall(self): |
244 """Build stub project.""" | 103 """Install NACL sdk.""" |
245 stub_project_files = [ | 104 # Executing naclsdk(.bat) list |
246 'build.scons', | 105 if pyauto.PyUITest.IsWin(): |
247 'scons' | 106 source_file = os.path.join( |
248 ] | 107 self._extracted_sdk_path, 'nacl_sdk', 'naclsdk.bat') |
249 project_template_path = self._GetDirectoryPath('project_templates', | 108 elif pyauto.PyUITest.IsMac() or pyauto.PyUITest.IsLinux(): |
250 self._extracted_sdk_path) | 109 source_file = os.path.join( |
251 examples_path = self._GetDirectoryPath('examples', | 110 self._extracted_sdk_path, 'nacl_sdk', 'naclsdk') |
252 self._extracted_sdk_path) | 111 subprocess.call(['chmod', '-R', '755', self._extracted_sdk_path]) |
253 init_project_path = os.path.join(project_template_path, 'init_project.py') | 112 else: |
113 self.fail(msg='NaCl SDK does not support this OS.') | |
114 subprocess.Popen([source_file, 'list'], | |
115 stdout=subprocess.PIPE, | |
116 stderr=subprocess.PIPE).communicate() | |
254 | 117 |
255 # Build a C project. | 118 def _VerifyUpdate(self): |
256 subprocess.call( | 119 """Update NACL sdk""" |
257 ['python', init_project_path, '-n', 'hello_c', '-c', '-d', | 120 # Executing naclsdk(.bat) update |
258 examples_path], stdout=subprocess.PIPE) | 121 if pyauto.PyUITest.IsWin(): |
259 | 122 source_file = os.path.join(self._extracted_sdk_path, 'nacl_sdk', |
260 hello_c_path = os.path.join(examples_path, 'hello_c') | 123 'naclsdk.bat') |
261 for file in stub_project_files: | 124 elif pyauto.PyUITest.IsMac() or pyauto.PyUITest.IsLinux(): |
262 self.assertTrue(self._HasFile(file, hello_c_path), | 125 source_file = os.path.join(self._extracted_sdk_path, 'nacl_sdk', |
263 msg='Cannot build C stub project.') | 126 'naclsdk') |
264 | 127 else: |
265 # Build a C++ project. | 128 self.fail(msg='NaCl SDK does not support this OS.') |
266 subprocess.call( | 129 # Executing nacl_sdk(.bat) update to get the latest version. |
267 ['python', init_project_path, '-n', 'hello_cc', '-d', | 130 updated_output = subprocess.Popen([source_file, 'update'], |
268 examples_path], stdout=subprocess.PIPE) | 131 stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0] |
269 | 132 self._updated_pepper_versions.extend( |
270 hello_cc_path = os.path.join(examples_path, 'hello_cc') | 133 re.findall('Updating bundle (pepper_[0-9]{2})', updated_output)) |
271 for file in stub_project_files: | 134 self._updated_pepper_versions = list(set(self._updated_pepper_versions)) |
272 self.assertTrue(self._HasFile(file, hello_cc_path), | 135 self._updated_pepper_versions.sort(key=str.lower) |
273 msg='Cannot build C++ stub project.') | 136 updated_pepper_versions_len = len(self._updated_pepper_versions) |
137 self._latest_updated_pepper_versions = filter( | |
138 lambda x: x >= 'pepper_18', self._updated_pepper_versions) | |
274 | 139 |
275 def _LaunchServerAndVerifyExamples(self): | 140 def _LaunchServerAndVerifyExamples(self): |
276 """Start local HTTP server and verify examples.""" | 141 """Start local HTTP server and verify examples.""" |
277 # Make sure server is not open. | 142 if self._ChromeAndPepperVersion(self._latest_updated_pepper_versions[0]): |
278 if self._IsURLAlive('http://localhost:5103'): | 143 examples_path = os.path.join(self._extracted_sdk_path, 'nacl_sdk', |
279 self._CloseHTTPServer() | 144 self._latest_updated_pepper_versions[0], |
145 'examples') | |
146 # Close server if it's already open. | |
147 if self._IsURLAlive('http://localhost:5103'): | |
148 self._CloseHTTPServer() | |
280 | 149 |
281 # Start HTTP server. | 150 # Launch local http server. |
282 examples_path = self._GetDirectoryPath('examples', | 151 proc = subprocess.Popen(['make RUN'], shell=True, cwd = examples_path) |
283 self._extracted_sdk_path) | 152 self.WaitUntil( |
284 if pyauto.PyUITest.IsWin(): | 153 lambda: self._IsURLAlive('http://localhost:5103'), |
285 http_path = os.path.join(examples_path, 'httpd.cmd') | 154 timeout=150, retry_sleep=1) |
286 proc = subprocess.Popen([http_path], cwd=examples_path) | 155 |
156 examples = { | |
157 'dynamic_library_open': 'http://localhost:5103/dlopen/dlopen.html', | |
158 'geturl': 'http://localhost:5103/geturl/geturl.html', | |
159 'input_events': 'http://localhost:5103/input_events' | |
160 '/input_events.html', | |
161 'load_progress': | |
162 'http://localhost:5103/load_progress/load_progress.html', | |
163 'multithreaded_input_events': | |
164 'http://localhost:5103/multithreaded_input_events' | |
165 '/mt_input_events.html', | |
166 'pi_generator': | |
167 'http://localhost:5103/pi_generator/pi_generator.html', | |
168 'sine_synth': 'http://localhost:5103/sine_synth/sine_synth.html', | |
169 'web_socket': | |
170 'http://localhost:5103/websocket/websocket.html', | |
171 } | |
172 try: | |
173 self._OpenExamplesAndStartTest(examples) | |
174 finally: | |
175 self._CloseHTTPServer(proc) | |
176 | |
287 else: | 177 else: |
288 http_path = os.path.join(examples_path, 'httpd.py') | 178 self.fail(msg='Pepper Version %s doesnot match the Chrome version %s.' |
289 proc = subprocess.Popen(['python', http_path], cwd=examples_path) | 179 % (self._latest_updated_pepper_versions[0], |
180 self.GetBrowserInfo()['properties']['ChromeVersion'])) | |
290 | 181 |
291 success = self.WaitUntil( | 182 def _ChromeAndPepperVersion(self, pepper_version='pepper_18'): |
292 lambda: self._IsURLAlive('http://localhost:5103'), | 183 """Determine if chrome and pepper version matach""" |
293 timeout=30, retry_sleep=1, expect_retval=True) | 184 version_number = re.findall('pepper_([0-9]{2})', pepper_version) |
294 self.assertTrue(success, | 185 browser_info = self.GetBrowserInfo() |
295 msg='Cannot open HTTP server. %s' % | 186 chrome_version = browser_info['properties']['ChromeVersion'] |
296 self.GetActiveTabTitle()) | 187 chrome_build = int(chrome_version.split('.')[0]) |
297 | 188 return int(chrome_build) == int(version_number[0]) |
298 examples = { | |
299 'hello_world_c': 'http://localhost:5103/hello_world_c/' | |
300 'hello_world.html', | |
301 'hello_world': 'http://localhost:5103/hello_world/hello_world.html', | |
302 'geturl': 'http://localhost:5103/geturl/geturl.html', | |
303 'pi_generator': 'http://localhost:5103/pi_generator/pi_generator.html', | |
304 'sine_synth': 'http://localhost:5103/sine_synth/sine_synth.html', | |
305 } | |
306 try: | |
307 self._OpenExamplesAndStartTest(examples) | |
308 finally: | |
309 self._CloseHTTPServer(proc) | |
310 | |
311 def _VerifyRebuildExamples(self): | |
312 """Re-build the examples and verify they are as expected.""" | |
313 example_dirs = [ | |
314 'geturl', | |
315 'hello_world', | |
316 'hello_world_c', | |
317 'pi_generator', | |
318 'sine_synth' | |
319 ] | |
320 examples_path = self._GetDirectoryPath('examples', | |
321 self._extracted_sdk_path) | |
322 | |
323 scons_path = os.path.join(examples_path, 'scons -c') | |
324 subprocess.call([scons_path], cwd=examples_path, shell=True) | |
325 for x in example_dirs: | |
326 ex_path = os.path.join(examples_path, x) | |
327 self.assertFalse(self._HasFile('*.nmf', ex_path), | |
328 msg='Failed running scons -c.') | |
329 | |
330 scons_path = os.path.join(examples_path, 'scons') | |
331 proc = subprocess.Popen([scons_path], cwd=examples_path, | |
332 stdout=subprocess.PIPE, shell=True) | |
333 proc.communicate() | |
334 | |
335 # Verify each example directory contains .nmf file. | |
336 for dir in example_dirs: | |
337 dir = os.path.join(examples_path, dir) | |
338 if not self._HasFile('*.nmf', dir): | |
339 self.fail(msg='Failed running scons.') | |
340 | |
341 self._LaunchServerAndVerifyExamples() | |
342 | |
343 def _VerifySelldrAndNcval(self): | |
344 """Verify sel_ldr and ncval.""" | |
345 architecture = self._GetPlatformArchitecture() | |
346 scons_arg = None | |
347 if pyauto.PyUITest.IsWin(): | |
348 if architecture == '64bit': | |
349 scons_arg = 'test64' | |
350 else: | |
351 scons_arg = 'test32' | |
352 elif pyauto.PyUITest.IsMac(): | |
353 scons_arg = 'test64' | |
354 elif pyauto.PyUITest.IsLinux(): | |
355 scons_arg = 'test64' | |
356 | |
357 examples_path = self._GetDirectoryPath('examples', | |
358 self._extracted_sdk_path) | |
359 scons_path = os.path.join(examples_path, 'scons ' + scons_arg) | |
360 | |
361 # Build and run the unit test. | |
362 proc = subprocess.Popen([scons_path], stdout=subprocess.PIPE, | |
363 shell=True, cwd=examples_path) | |
364 lines = proc.communicate()[0].splitlines() | |
365 test_ran = False | |
366 | |
367 for line in lines: | |
368 if 'Check:' in line: | |
369 self.assertTrue('passed' in line, | |
370 msg='Nacl-sel_ldr unit test failed.') | |
371 test_ran = True | |
372 self.assertTrue(test_ran, | |
373 msg='Failed to build and run nacl-sel_ldr unit test.') | |
374 | |
375 if architecture == '64bit': | |
376 self.assertTrue( | |
377 self._HasPathInTree('hello_world_x86_64.nexe', | |
378 True, root=examples_path), | |
379 msg='Missing file: hello_world_x86_64.nexe.') | |
380 else: | |
381 self.assertTrue( | |
382 self._HasPathInTree('hello_world_x86_32.nexe', | |
383 True, root=examples_path), | |
384 msg='Missing file: hello_world_x86_32.nexe.') | |
385 | |
386 # Verify that a mismatch of sel_ldr and .nexe produces an error. | |
387 toolchain_path = self._GetDirectoryPath('toolchain', | |
388 self._extracted_sdk_path) | |
389 bin_path = self._GetDirectoryPath('bin', toolchain_path) | |
390 hello_world_path = self._GetDirectoryPath('hello_world', examples_path) | |
391 sel_32_path = os.path.join(bin_path, 'sel_ldr_x86_32') | |
392 sel_64_path = os.path.join(bin_path, 'sel_ldr_x86_64') | |
393 nexe_32_path = os.path.join(hello_world_path, 'hello_world_x86_32.nexe') | |
394 nexe_64_path = os.path.join(hello_world_path, 'hello_world_x86_64.nexe') | |
395 | |
396 if architecture == '64bit': | |
397 success = self._RunProcessAndCheckOutput( | |
398 [sel_64_path, nexe_32_path], 'Error while loading') | |
399 else: | |
400 success = self._RunProcessAndCheckOutput( | |
401 [sel_32_path, nexe_64_path], 'Error while loading') | |
402 self.assertTrue(success, | |
403 msg='Failed to verify sel_ldr and .nexe mismatch.') | |
404 | |
405 # Run the appropriate ncval for the platform on the matching .nexe. | |
406 ncval_32_path = os.path.join(bin_path, 'ncval_x86_32') | |
407 ncval_64_path = os.path.join(bin_path, 'ncval_x86_64') | |
408 | |
409 if architecture == '64bit': | |
410 success = self._RunProcessAndCheckOutput( | |
411 [ncval_64_path, nexe_64_path], 'is safe') | |
412 else: | |
413 success = self._RunProcessAndCheckOutput( | |
414 [ncval_32_path, nexe_32_path], 'is safe') | |
415 self.assertTrue(success, msg='Failed to verify ncval.') | |
416 | |
417 # Verify that a mismatch of ncval and .nexe produces an error. | |
418 if architecture == '64bit': | |
419 success = self._RunProcessAndCheckOutput( | |
420 [ncval_64_path, nexe_32_path], 'is safe', is_in=False) | |
421 else: | |
422 success = self._RunProcessAndCheckOutput( | |
423 [ncval_32_path, nexe_64_path], 'is safe', is_in=False) | |
424 self.assertTrue(success, msg='Failed to verify ncval and .nexe mismatch.') | |
425 | 189 |
426 def _RemoveDownloadedTestFile(self): | 190 def _RemoveDownloadedTestFile(self): |
427 """Delete downloaded files and dirs from downloads directory.""" | 191 """Delete downloaded files and dirs from downloads directory.""" |
428 if self._extracted_sdk_path and os.path.exists(self._extracted_sdk_path): | 192 if self._extracted_sdk_path and os.path.exists(self._extracted_sdk_path): |
429 self._CloseHTTPServer() | 193 self._CloseHTTPServer() |
430 | 194 |
431 def _RemoveFile(): | 195 def _RemoveFile(): |
432 shutil.rmtree(self._extracted_sdk_path, ignore_errors=True) | 196 shutil.rmtree(self._extracted_sdk_path, ignore_errors=True) |
433 return os.path.exists(self._extracted_sdk_path) | 197 return os.path.exists(self._extracted_sdk_path) |
434 | 198 |
435 success = self.WaitUntil(_RemoveFile, retry_sleep=2, expect_retval=False) | 199 success = self.WaitUntil(_RemoveFile, retry_sleep=2, |
200 expect_retval=False) | |
436 self.assertTrue(success, | 201 self.assertTrue(success, |
437 msg='Cannot remove %s' % self._extracted_sdk_path) | 202 msg='Cannot remove %s' % self._extracted_sdk_path) |
438 | 203 |
439 if self._temp_dir: | 204 if self._temp_dir: |
440 pyauto_utils.RemovePath(self._temp_dir) | 205 pyauto_utils.RemovePath(self._temp_dir) |
441 | 206 |
442 def _RunProcessAndCheckOutput(self, args, look_for, is_in=True): | |
443 """Run process and look for string in output. | |
444 | |
445 Args: | |
446 args: Argument strings to pass to subprocess. | |
447 look_for: The string to search in output. | |
448 is_in: True if checking if param look_for is in output. | |
449 False if checking if param look_for is not in output. | |
450 | |
451 Returns: | |
452 True, if output contains parameter |look_for| and |is_in| is True, or | |
453 False otherwise. | |
454 """ | |
455 proc = subprocess.Popen(args, stdout=subprocess.PIPE, | |
456 stderr=subprocess.PIPE) | |
457 (stdout, stderr) = proc.communicate() | |
458 lines = (stdout + stderr).splitlines() | |
459 for line in lines: | |
460 if look_for in line: | |
461 return is_in | |
462 return not is_in | |
463 | |
464 def _OpenExamplesAndStartTest(self, examples): | 207 def _OpenExamplesAndStartTest(self, examples): |
465 """Open each example and verify that it's working. | 208 """Open each example and verify that it's working. |
466 | 209 |
467 Args: | 210 Args: |
468 examples: A dict of name to url of examples. | 211 examples: A dict of name to url of examples. |
469 """ | 212 """ |
470 self._EnableNaClPlugin() | |
471 | |
472 # Open all examples. | 213 # Open all examples. |
473 for name, url in examples.items(): | 214 for name, url in examples.items(): |
474 self.AppendTab(pyauto.GURL(url)) | 215 self.AppendTab(pyauto.GURL(url)) |
475 self._CheckForCrashes() | 216 self._CheckForCrashes() |
476 | 217 |
477 # Verify all examples are working. | 218 # Verify all examples are working. |
478 for name, url in examples.items(): | 219 for name, url in examples.items(): |
479 self._VerifyAnExample(name, url) | 220 self._VerifyAnExample(name, url) |
480 self._CheckForCrashes() | 221 self._CheckForCrashes() |
481 | 222 |
482 # Reload all examples. | |
483 for _ in range(2): | |
484 for tab_index in range(self.GetTabCount()): | |
485 self.GetBrowserWindow(0).GetTab(tab_index).Reload() | |
486 self._CheckForCrashes() | |
487 | |
488 # Verify all examples are working. | |
489 for name, url in examples.items(): | |
490 self._VerifyAnExample(name, url) | |
491 self._CheckForCrashes() | |
492 | |
493 # Close each tab, check for crashes and verify all open | 223 # Close each tab, check for crashes and verify all open |
494 # examples operate correctly. | 224 # examples operate correctly. |
495 tab_count = self.GetTabCount() | 225 tab_count = self.GetTabCount() |
496 for index in xrange(tab_count - 1, 0, -1): | 226 for index in xrange(tab_count - 1, 0, -1): |
497 self.GetBrowserWindow(0).GetTab(index).Close(True) | 227 self.GetBrowserWindow(0).GetTab(index).Close(True) |
498 self._CheckForCrashes() | 228 self._CheckForCrashes() |
499 | 229 |
500 tabs = self.GetBrowserInfo()['windows'][0]['tabs'] | 230 tabs = self.GetBrowserInfo()['windows'][0]['tabs'] |
501 for tab in tabs: | 231 for tab in tabs: |
502 if tab['index'] > 0: | 232 if tab['index'] > 0: |
503 for name, url in examples.items(): | 233 for name, url in examples.items(): |
504 if url == tab['url']: | 234 if url == tab['url']: |
505 self._VerifyAnExample(name, url) | 235 self._VerifyAnExample(name, url) |
506 break | 236 break |
507 | 237 |
508 def _VerifyAnExample(self, name, url): | 238 def _VerifyAnExample(self, name, url): |
509 """Verify NaCl example is working. | 239 """Verify NaCl example is working. |
510 | 240 |
511 Args: | 241 Args: |
512 name: A string name of the example. | 242 name: A string name of the example. |
513 url: A string url of the example. | 243 url: A string url of the example. |
514 """ | 244 """ |
515 available_example_tests = { | 245 available_example_tests = { |
516 'hello_world_c': self._VerifyHelloWorldExample, | 246 'dynamic_library_open': self._VerifyDynamicLibraryOpen, |
517 'hello_world': self._VerifyHelloWorldExample, | 247 'geturl': self._VerifyGetURLExample, |
518 'pi_generator': self._VerifyPiGeneratorExample, | 248 'input_events': self._VerifyInputEventsExample, |
519 'sine_synth': self._VerifySineSynthExample, | 249 'load_progress': self._VerifyLoadProgressExample, |
520 'geturl': self._VerifyGetURLExample, | 250 'multithreaded_input_events': |
521 'life': self._VerifyConwaysLifeExample | 251 self._VerifyMultithreadedInputEventsExample, |
252 'pi_generator': self._VerifyPiGeneratorExample, | |
253 'sine_synth': self._VerifySineSynthExample, | |
254 'web_socket':self._VerifyWebSocketExample, | |
522 } | 255 } |
523 | 256 |
524 if not name in available_example_tests: | 257 if not name in available_example_tests: |
525 self.fail(msg='No test available for %s.' % name) | 258 self.fail(msg='No test available for %s.' % name) |
526 | 259 |
527 info = self.GetBrowserInfo() | 260 info = self.GetBrowserInfo() |
528 tabs = info['windows'][0]['tabs'] | 261 tabs = info['windows'][0]['tabs'] |
529 tab_index = None | 262 tab_index = None |
530 for tab in tabs: | 263 for tab in tabs: |
531 if url == tab['url']: | 264 if url == tab['url']: |
532 self.ActivateTab(tab['index']) | 265 self.ActivateTab(tab['index']) |
533 tab_index = tab['index'] | 266 tab_index = tab['index'] |
534 break | 267 break |
535 | 268 |
536 if tab_index: | 269 if tab_index: |
537 available_example_tests[name](tab_index, name, url) | 270 available_example_tests[name](tab_index, name, url) |
538 | 271 |
539 def _VerifyHelloWorldExample(self, tab_index, name, url): | 272 def _VerifyElementPresent(self, element_id, expected_value, tab_index, |
540 """Verify Hello World Example. | 273 attribute = 'innerHTML', timeout = 30): |
274 """Determine if Dom element has the expected value. | |
275 | |
276 Args: | |
277 element_id: Dom element's id. | |
278 expected_value: String to be matched against the Dom element. | |
279 tab_index: Tab index to work on. | |
280 attribute: Attribute to match |expected_value| against, if | |
281 given. Defaults to 'innerHTML'. | |
282 timeout: The max timeout (in secs) for which to wait. | |
283 """ | |
284 js_code = """ | |
285 var output = document.getElementById("%s").%s; | |
286 var result; | |
287 if (output.indexOf("%s") != -1) | |
288 result = "pass"; | |
289 else | |
290 result = "fail"; | |
291 window.domAutomationController.send(result); | |
292 """ % (element_id, attribute, expected_value) | |
293 success = self.WaitUntil( | |
294 lambda: self.ExecuteJavascript(js_code, tab_index), | |
295 timeout=timeout, expect_retval='pass') | |
296 return success | |
297 | |
298 def _CreateJSToSimulateMouseclick(self): | |
299 """Create javascript to simulate mouse click event.""" | |
300 js_code = """ | |
301 var rightClick = document.createEvent('MouseEvents'); | |
302 rightClick.initMouseEvent( | |
303 'mousedown', true, true, document, | |
304 1, 32, 121, 10, 100, | |
305 false, false, false, false, | |
306 2, document.getElementById('event_module') | |
307 ); | |
308 document.getElementById("event_module").dispatchEvent(rightClick); | |
309 window.domAutomationController.send("done"); | |
310 """ | |
311 return js_code | |
312 | |
313 def _VerifyInputEventsExample(self, tab_index, name, url): | |
314 """Verify Input Events Example. | |
541 | 315 |
542 Args: | 316 Args: |
543 tab_index: Tab index integer that the example is on. | 317 tab_index: Tab index integer that the example is on. |
544 name: A string name of the example. | 318 name: A string name of the example. |
545 url: A string url of the example. | 319 url: A string url of the example. |
546 """ | 320 """ |
547 success = self.WaitUntil( | 321 success = self._VerifyElementPresent('eventString', 'DidChangeView', |
548 lambda: self.GetDOMValue( | 322 tab_index,timeout=60) |
549 'document.getElementById("statusField").innerHTML', | |
550 tab_index), | |
551 timeout=60, expect_retval='SUCCESS') | |
552 self.assertTrue(success, msg='Example %s failed. URL: %s' % (name, url)) | 323 self.assertTrue(success, msg='Example %s failed. URL: %s' % (name, url)) |
553 | 324 |
554 js_code = """ | 325 # Simulate mouse click on event module. |
555 window.alert = function(e) { | 326 js_code = self._CreateJSToSimulateMouseclick() |
556 window.domAutomationController.send(String(e)); | |
557 } | |
558 window.domAutomationController.send("done"); | |
559 """ | |
560 self.ExecuteJavascript(js_code, tab_index) | 327 self.ExecuteJavascript(js_code, tab_index) |
561 | 328 |
562 result = self.ExecuteJavascript('document.helloForm.elements[1].click();', | 329 # Check if 'eventString' has handled above mouse click. |
563 tab_index) | 330 success = self.WaitUntil( |
564 self.assertEqual(result, '42', | 331 lambda: re.search('DidHandleInputEvent', self.GetDOMValue( |
565 msg='Example %s failed. URL: %s' % (name, url)) | 332 'document.getElementById("eventString").innerHTML', |
333 tab_index)).group(), timeout=30, expect_retval='DidHandleInputEvent') | |
334 self.assertTrue(success, msg='Example %s failed. URL: %s' % (name, url)) | |
566 | 335 |
567 result = self.ExecuteJavascript('document.helloForm.elements[2].click();', | 336 def _VerifyMultithreadedInputEventsExample(self, tab_index, name, url): |
568 tab_index) | 337 """Verify Input Events Example. |
569 self.assertEqual(result, 'dlrow olleH', | |
570 msg='Example %s failed. URL: %s' % (name, url)) | |
571 | |
572 def _VerifyPiGeneratorExample(self, tab_index, name, url): | |
573 """Verify Pi Generator Example. | |
574 | 338 |
575 Args: | 339 Args: |
576 tab_index: Tab index integer that the example is on. | 340 tab_index: Tab index integer that the example is on. |
577 name: A string name of the example. | |
578 url: A string url of the example. | |
579 """ | |
580 success = self.WaitUntil( | |
581 lambda: self.GetDOMValue('document.form.pi.value', 0, tab_index)[0:3], | |
582 timeout=120, expect_retval='3.1') | |
583 self.assertTrue(success, msg='Example %s failed. URL: %s' % (name, url)) | |
584 | |
585 # Get top corner of Pi image. | |
586 js_code = """ | |
587 var obj = document.getElementById('piGenerator'); | |
588 var curleft = curtop = 0; | |
589 do { | |
590 curleft += obj.offsetLeft; | |
591 curtop += obj.offsetTop; | |
592 } while (obj = obj.offsetParent); | |
593 window.domAutomationController.send(curleft + ", " + curtop); | |
594 """ | |
595 result = self.ExecuteJavascript(js_code, tab_index) | |
596 result_split = result.split(', ') | |
597 x = int(result_split[0]) | |
598 y = int(result_split[1]) | |
599 window = self.GetBrowserInfo()['windows'][0] | |
600 window_to_content_x = 2 | |
601 window_to_content_y = 80 | |
602 pi_image_x = x + window['x'] + window_to_content_x | |
603 pi_image_y = y + window['y'] + window_to_content_y | |
604 | |
605 if self._IsGetPixelSupported(): | |
606 is_animating = self._IsColorChanging(pi_image_x, pi_image_y, 50, 50) | |
607 self.assertTrue(is_animating, | |
608 msg='Example %s failed. URL: %s' % (name, url)) | |
609 | |
610 def _VerifySineSynthExample(self, tab_index, name, url): | |
611 """Verify Sine Wave Synthesizer Example. | |
612 | |
613 Args: | |
614 tab_index: Tab index integer that the example is on. | |
615 name: A string name of the example. | 341 name: A string name of the example. |
616 url: A string url of the example. | 342 url: A string url of the example. |
617 """ | 343 """ |
618 success = self.WaitUntil( | 344 success = self.WaitUntil( |
345 lambda: bool(self.GetDOMValue( | |
346 'document.getElementById("eventString").innerHTML', | |
347 tab_index).find('DidChangeView') + 1), timeout=30) | |
348 | |
349 self.assertTrue(success, msg='Example %s failed. URL: %s' % (name, url)) | |
350 | |
351 # Simulate mouse click on event module. | |
352 js_code = self._CreateJSToSimulateMouseclick() | |
353 self.ExecuteJavascript(js_code, tab_index) | |
354 | |
355 # Check if above mouse click is handled. | |
356 success = self._VerifyElementPresent('eventString', 'Mouse event', | |
357 tab_index) | |
358 self.assertTrue(success, msg='Example %s failed. URL: %s' % (name, url)) | |
359 | |
360 # Kill worker thread and queue | |
361 js_code = """ | |
362 document.getElementsByTagName('button')[0].click(); | |
363 window.domAutomationController.send("done"); | |
364 """ | |
365 self.ExecuteJavascript(js_code, tab_index) | |
366 | |
367 # Check if main thread has cancelled queue. | |
368 success = self._VerifyElementPresent('eventString', 'Received cancel', | |
369 tab_index) | |
370 self.assertTrue(success, msg='Example %s failed. URL: %s' % (name, url)) | |
371 | |
372 # Simulate mouse click on event module. | |
373 js_code = self._CreateJSToSimulateMouseclick() | |
374 self.ExecuteJavascript(js_code, tab_index) | |
375 | |
376 # Check if above mouse click is not handled after killing worker thread. | |
377 def _CheckMouseClickEventStatus(): | |
378 return self.GetDOMValue( | |
379 'document.getElementById("eventString").innerHTML', | |
380 tab_index).find('Mouse event', self.GetDOMValue( | |
381 'document.getElementById("eventString").innerHTML', tab_index).find( | |
382 'Received cancel')) | |
383 | |
384 success = self.WaitUntil(_CheckMouseClickEventStatus, timeout=30, | |
385 expect_retval=-1) | |
386 self.assertTrue(success, msg='Example %s failed. URL: %s' % (name, url)) | |
387 | |
388 def _VerifyWebSocketExample(self, tab_index, name, url): | |
389 """Verify Web Socket Open Example. | |
390 | |
391 Args: | |
392 tab_index: Tab index integer that the example is on. | |
393 name: A string name of the example. | |
394 url: A string url of the example. | |
395 """ | |
396 # Check if example is loaded. | |
397 success = self.WaitUntil( | |
398 lambda: self.GetDOMValue( | |
399 'document.getElementById("statusField").innerHTML', tab_index), | |
400 timeout=60, expect_retval='SUCCESS') | |
401 self.assertTrue(success, msg='Example %s failed. URL: %s' % (name, url)) | |
402 | |
403 # Simulate clicking on Connect button to establish a connection. | |
404 js_code = """ | |
405 document.getElementsByTagName('input')[1].click(); | |
406 window.domAutomationController.send("done"); | |
407 """ | |
408 self.ExecuteJavascript(js_code, tab_index) | |
409 | |
410 # Check if connected | |
411 success = self._VerifyElementPresent('log', 'connected', tab_index, | |
412 'value') | |
413 self.assertTrue(success, msg='Example %s failed. URL: %s' % (name, url)) | |
414 | |
415 # Simulate clicking on Send button to send text message in log. | |
416 js_code = """ | |
417 document.getElementsByTagName('input')[3].click(); | |
418 window.domAutomationController.send("done"); | |
419 """ | |
420 self.ExecuteJavascript(js_code, tab_index) | |
421 success = self.WaitUntil( | |
422 lambda: bool(re.search('send:', self.GetDOMValue( | |
423 'document.getElementById("log").value', tab_index))), | |
424 timeout=30) | |
425 self.assertTrue(success, msg='Example %s failed. URL: %s' % (name, url)) | |
426 | |
427 def _VerifyDynamicLibraryOpen(self, tab_index, name, url): | |
428 """Verify Dynamic Library Open Example. | |
429 | |
430 Args: | |
431 tab_index: Tab index integer that the example is on. | |
432 name: A string name of the example. | |
433 url: A string url of the example. | |
434 """ | |
435 # Check if example is loaded. | |
436 success = self._VerifyElementPresent('consolec', 'Eightball loaded', | |
437 tab_index, | |
438 timeout=60) | |
439 self.assertTrue(success, msg='Example %s failed. URL: %s' % (name, url)) | |
440 | |
441 # Simulate clicking on ASK button and check answer log for desired answer. | |
442 js_code = """ | |
443 document.getElementsByTagName('input')[1].click(); | |
444 window.domAutomationController.send("done"); | |
445 """ | |
446 self.ExecuteJavascript(js_code, tab_index) | |
447 def _CheckAnswerLog(): | |
448 return bool(re.search(r'NO|YES|42|MAYBE NOT|DEFINITELY|' | |
449 'ASK ME TOMORROW|MAYBE|PARTLY CLOUDY', | |
450 self.GetDOMValue('document.getElementById("answerlog").innerHTML', | |
451 tab_index))) | |
452 | |
453 success = self.WaitUntil(_CheckAnswerLog, timeout=30) | |
454 self.assertTrue(success, msg='Example %s failed. URL: %s' % (name, url)) | |
455 | |
456 def _VerifyLoadProgressExample(self, tab_index, name, url): | |
457 """Verify Dynamic Library Open Example. | |
458 | |
459 Args: | |
460 tab_index: Tab index integer that the example is on. | |
461 name: A string name of the example. | |
462 url: A string url of the example. | |
463 """ | |
464 # Check if example loads and displays loading progress. | |
465 success = self.WaitUntil( | |
466 lambda: self.GetDOMValue( | |
467 'document.getElementById("status_field").innerHTML', tab_index), | |
468 timeout=60, expect_retval='SUCCESS') | |
469 self.assertTrue(success, msg='Example %s failed. URL: %s' % (name, url)) | |
470 | |
471 def _CheckLoadProgressStatus(): | |
472 return re.search(r'(loadstart).+(progress:).+(load).+(loadend).+(lastError :)', | |
473 self.GetDOMValue('document.getElementById("event_log_field").innerHTML' | |
474 , tab_index)) | |
475 success = self.WaitUntil(_CheckLoadProgressStatus, timeout=10) | |
476 self.assertTrue(success, msg='Example %s failed. URL: %s' % (name, url)) | |
477 | |
478 def _VerifyPiGeneratorExample(self, tab_index, name, url): | |
479 """Verify Pi Generator Example. | |
480 | |
481 Args: | |
482 tab_index: Tab index integer that the example is on. | |
483 name: A string name of the example. | |
484 url: A string url of the example. | |
485 """ | |
486 success = self.WaitUntil( | |
487 lambda: self.GetDOMValue('document.form.pi.value', tab_index)[0:3], | |
488 timeout=30, expect_retval='3.1') | |
489 self.assertTrue(success, msg='Example %s failed. URL: %s' % (name, url)) | |
490 | |
491 def _VerifySineSynthExample(self, tab_index, name, url): | |
492 """Verify Sine Wave Synthesizer Example. | |
493 | |
494 Args: | |
495 tab_index: Tab index integer that the example is on. | |
496 name: A string name of the example. | |
497 url: A string url of the example. | |
498 """ | |
499 success = self.WaitUntil( | |
619 lambda: self.GetDOMValue( | 500 lambda: self.GetDOMValue( |
620 'document.getElementById("frequency_field").value', | 501 'document.getElementById("frequency_field").value', |
621 tab_index), | 502 tab_index),timeout=40, expect_retval='440') |
Nirnimesh
2012/05/11 09:13:32
why do you want to specify the timeout var? Isn't
pnihalani1
2012/05/11 17:20:08
30s is not enough,it takes longer.
On 2012/05/11 0
| |
622 timeout=30, expect_retval='440') | |
623 self.assertTrue(success, msg='Example %s failed. URL: %s' % (name, url)) | 503 self.assertTrue(success, msg='Example %s failed. URL: %s' % (name, url)) |
624 | |
625 self.ExecuteJavascript( | 504 self.ExecuteJavascript( |
626 'document.body.getElementsByTagName("button")[0].click();' | 505 'document.body.getElementsByTagName("button")[0].click();' |
627 'window.domAutomationController.send("done")', | 506 'window.domAutomationController.send("done")', |
628 tab_index) | 507 tab_index) |
629 | 508 |
630 # TODO(chrisphan): Verify sound. | |
631 | |
632 def _VerifyGetURLExample(self, tab_index, name, url): | 509 def _VerifyGetURLExample(self, tab_index, name, url): |
633 """Verify GetURL Example. | 510 """Verify GetURL Example. |
634 | 511 |
635 Args: | 512 Args: |
636 tab_index: Tab index integer that the example is on. | 513 tab_index: Tab index integer that the example is on. |
637 name: A string name of the example. | 514 name: A string name of the example. |
638 url: A string url of the example. | 515 url: A string url of the example. |
639 """ | 516 """ |
640 success = self.WaitUntil( | 517 success = self.WaitUntil( |
641 lambda: self.GetDOMValue( | 518 lambda: self.GetDOMValue( |
642 'document.getElementById("status_field").innerHTML', | 519 'document.getElementById("status_field").innerHTML', |
Nirnimesh
2012/05/11 09:13:32
use ' instead of "
pnihalani1
2012/05/11 17:20:08
The double quote is inside single quote.
Do you wa
| |
643 tab_index), | 520 tab_index),timeout = 60, expect_retval='SUCCESS') |
Nirnimesh
2012/05/11 09:13:32
need a space after ,
Remove space around =
(Named
pnihalani1
2012/05/11 17:20:08
Done.
| |
644 timeout=60, expect_retval='SUCCESS') | |
645 self.assertTrue(success, msg='Example %s failed. URL: %s' % (name, url)) | 521 self.assertTrue(success, msg='Example %s failed. URL: %s' % (name, url)) |
646 | |
647 self.ExecuteJavascript( | 522 self.ExecuteJavascript( |
648 'document.geturl_form.elements[0].click();' | 523 'document.geturl_form.elements[0].click();' |
649 'window.domAutomationController.send("done")', | 524 'window.domAutomationController.send("done")', |
650 tab_index) | 525 tab_index) |
651 | 526 success = self._VerifyElementPresent('general_output', 'test passed', |
652 js_code = """ | 527 tab_index,timeout=60) |
653 var output = document.getElementById("general_output").innerHTML; | |
654 var result; | |
655 if (output.indexOf("test passed") != -1) | |
656 result = "pass"; | |
657 else | |
658 result = "fail"; | |
659 window.domAutomationController.send(result); | |
660 """ | |
661 success = self.WaitUntil( | |
662 lambda: self.ExecuteJavascript(js_code, tab_index), | |
663 timeout=30, expect_retval='pass') | |
664 self.assertTrue(success, msg='Example %s failed. URL: %s' % (name, url)) | 528 self.assertTrue(success, msg='Example %s failed. URL: %s' % (name, url)) |
665 | 529 |
666 def _VerifyConwaysLifeExample(self, tab_index, name, url): | 530 def _CheckForCrashes(self): |
667 """Verify Conway's Life Example. | 531 """Check for any browser/tab crashes and hangs.""" |
668 | |
669 Args: | |
670 tab_index: Tab index integer that the example is on. | |
671 name: A string name of the example. | |
672 url: A string url of the example. | |
673 """ | |
674 window = self.GetBrowserInfo()['windows'][0] | |
675 window_to_content_x = 2 | |
676 window_to_content_y = 80 | |
677 x = window['x'] + window_to_content_x | |
678 y = window['y'] + window_to_content_y | |
679 offset_pixel = 100 | |
680 if self._IsGetPixelSupported(): | |
681 success = self.WaitUntil( | |
682 lambda: self._GetPixel(x + offset_pixel, y + offset_pixel), | |
683 timeout=30, expect_retval=16777215) | |
684 self.assertTrue(success, msg='Example %s failed. URL: %s' % (name, url)) | |
685 | |
686 def _GetXMLNodeData(self, nodelist): | |
687 rc = [] | |
688 for node in nodelist: | |
689 if node.nodeType == node.TEXT_NODE: | |
690 rc.append(node.data) | |
691 return ''.join(rc) | |
692 | |
693 def _IsColorChanging(self, x, y, width, height): | |
694 """Check screen for anything that is moving. | |
695 | |
696 Args: | |
697 x: X coordinate on the screen. | |
698 y: Y coordinate on the screen. | |
699 width: Width of the area to scan. | |
700 height: Height of the area to scan. | |
701 | |
702 Returns: | |
703 True, if pixel color in area is changing, or | |
704 False otherwise. | |
705 """ | |
706 color_a = self._GetAreaPixelColor(x, y, width, height) | |
707 def _HasColorChanged(): | |
708 color_b = self._GetAreaPixelColor(x, y, width, height) | |
709 return color_a != color_b | |
710 | |
711 return self.WaitUntil(_HasColorChanged, timeout=6, | |
712 retry_sleep=2, expect_retval=True) | |
713 | |
714 def _IsGetPixelSupported(self): | |
715 """Check if get pixel is supported. | |
716 | |
717 Returns: | |
718 True, if get pixel is supported, or | |
719 False otherwise. | |
720 """ | |
721 return pyauto.PyUITest.IsWin() | |
722 | |
723 def _GetAreaPixelColor(self, x, y, width, height): | |
724 """Get an area of pixel color and return a list of color code values. | |
725 | |
726 Args: | |
727 x: X coordinate on the screen. | |
728 y: Y coordinate on the screen. | |
729 width: Width of the area to scan. | |
730 height: Height of the area to scan. | |
731 | |
732 Returns: | |
733 A list containing color codes. | |
734 """ | |
735 if pyauto.PyUITest.IsMac(): | |
736 pass # TODO(chrisphan): Do Mac. | |
737 elif pyauto.PyUITest.IsWin(): | |
738 return self._GetAreaPixelColorWin(x, y, width, height) | |
739 elif pyauto.PyUITest.IsLinux(): | |
740 pass # TODO(chrisphan): Do Linux. | |
741 return None | |
742 | |
743 def _GetAreaPixelColorWin(self, x, y, width, height): | |
744 """Get an area of pixel color for Windows and return a list. | |
745 | |
746 Args: | |
747 x: X coordinate on the screen. | |
748 y: Y coordinate on the screen. | |
749 width: Width of the area to scan. | |
750 height: Height of the area to scan. | |
751 | |
752 Returns: | |
753 A list containing color codes. | |
754 """ | |
755 colors = [] | |
756 hdc = ctypes.windll.user32.GetDC(0) | |
757 for x_pos in xrange(x, x + width, 1): | |
758 for y_pos in xrange(y, y + height, 1): | |
759 color = ctypes.windll.gdi32.GetPixel(hdc, x_pos, y_pos) | |
760 colors.append(color) | |
761 return colors | |
762 | |
763 def _GetPixel(self, x, y): | |
764 """Get pixel color at coordinate x and y. | |
765 | |
766 Args: | |
767 x: X coordinate on the screen. | |
768 y: Y coordinate on the screen. | |
769 | |
770 Returns: | |
771 An integer color code. | |
772 """ | |
773 if pyauto.PyUITest.IsMac(): | |
774 pass # TODO(chrisphan): Do Mac. | |
775 elif pyauto.PyUITest.IsWin(): | |
776 return self._GetPixelWin(x, y) | |
777 elif pyauto.PyUITest.IsLinux(): | |
778 pass # TODO(chrisphan): Do Linux. | |
779 return None | |
780 | |
781 def _GetPixelWin(self, x, y): | |
782 """Get pixel color at coordinate x and y for Windows | |
783 | |
784 Args: | |
785 x: X coordinate on the screen. | |
786 y: Y coordinate on the screen. | |
787 | |
788 Returns: | |
789 An integer color code. | |
790 """ | |
791 hdc = ctypes.windll.user32.GetDC(0) | |
792 color = ctypes.windll.gdi32.GetPixel(hdc, x, y) | |
793 return color | |
794 | |
795 def _CheckForCrashes(self, last_action=None, last_action_param=None): | |
796 """Check for any browser/tab crashes and hangs. | |
797 | |
798 Args: | |
799 last_action: Specify action taken before checking for crashes. | |
800 last_action_param: Parameter for last action. | |
801 """ | |
802 self.assertTrue(self.GetBrowserWindowCount(), | 532 self.assertTrue(self.GetBrowserWindowCount(), |
803 msg='Browser crashed, no window is open.') | 533 msg='Browser crashed, no window is open.') |
804 | 534 |
805 info = self.GetBrowserInfo() | 535 info = self.GetBrowserInfo() |
806 breakpad_folder = info['properties']['DIR_CRASH_DUMPS'] | 536 breakpad_folder = info['properties']['DIR_CRASH_DUMPS'] |
807 old_dmp_files = glob.glob(os.path.join(breakpad_folder, '*.dmp')) | 537 old_dmp_files = glob.glob(os.path.join(breakpad_folder, '*.dmp')) |
808 | 538 |
809 # Verify there're no crash dump files. | 539 # Verify there're no crash dump files. |
810 for dmp_file in glob.glob(os.path.join(breakpad_folder, '*.dmp')): | 540 for dmp_file in glob.glob(os.path.join(breakpad_folder, '*.dmp')): |
811 self.assertTrue(dmp_file in old_dmp_files, | 541 self.assertTrue(dmp_file in old_dmp_files, |
812 msg='Crash dump %s found' % dmp_file) | 542 msg='Crash dump %s found' % dmp_file) |
813 | 543 |
814 # Check for any crashed tabs. | 544 # Check for any crashed tabs. |
815 tabs = info['windows'][0]['tabs'] | 545 tabs = info['windows'][0]['tabs'] |
816 for tab in tabs: | 546 for tab in tabs: |
817 if tab['url'] != 'about:blank': | 547 if tab['url'] != 'about:blank': |
818 if not self.GetDOMValue('document.body.innerHTML', tab['index']): | 548 if not self.GetDOMValue('document.body.innerHTML', tab['index']): |
819 self.fail(msg='Tab crashed on %s' % tab['url']) | 549 self.fail(msg='Tab crashed on %s' % tab['url']) |
820 | 550 |
821 # TODO(chrisphan): Check for tab hangs and browser hangs. | |
822 # TODO(chrisphan): Handle specific action: close browser, close tab. | |
823 if last_action == 'close tab': | |
824 pass | |
825 elif last_action == 'close browser': | |
826 pass | |
827 else: | |
828 pass | |
829 | |
830 def _GetPlatformArchitecture(self): | 551 def _GetPlatformArchitecture(self): |
831 """Get platform architecture. | 552 """Get platform architecture. |
832 | 553 |
833 Args: | |
834 last_action: Last action taken before checking for crashes. | |
835 last_action_param: Parameter for last action. | |
836 | |
837 Returns: | 554 Returns: |
838 A string representing the platform architecture. | 555 A string representing the platform architecture. |
839 """ | 556 """ |
840 if pyauto.PyUITest.IsWin(): | 557 if pyauto.PyUITest.IsWin(): |
841 if os.environ['PROGRAMFILES'] == 'C:\\Program Files (x86)': | 558 if os.environ['PROGRAMFILES'] == 'C:\\Program Files (x86)': |
842 return '64bit' | 559 return '64bit' |
843 else: | 560 else: |
844 return '32bit' | 561 return '32bit' |
845 elif pyauto.PyUITest.IsMac() or pyauto.PyUITest.IsLinux(): | 562 elif pyauto.PyUITest.IsMac() or pyauto.PyUITest.IsLinux(): |
846 if platform.machine() == 'x86_64': | 563 if platform.machine() == 'x86_64': |
847 return '64bit' | 564 return '64bit' |
848 else: | 565 else: |
849 return '32bit' | 566 return '32bit' |
850 return '32bit' | 567 return '32bit' |
851 | 568 |
852 def _HasFile(self, pattern, root=os.curdir): | |
853 """Check if a file matching the specified pattern exists in a directory. | |
854 | |
855 Args: | |
856 pattern: Pattern of file name. | |
857 root: Directory to start looking. | |
858 | |
859 Returns: | |
860 True, if root contains the file name pattern, or | |
861 False otherwise. | |
862 """ | |
863 return len(glob.glob(os.path.join(root, pattern))) | |
864 | |
865 def _HasPathInTree(self, pattern, is_file, root=os.curdir): | 569 def _HasPathInTree(self, pattern, is_file, root=os.curdir): |
866 """Recursively checks if a file/directory matching a pattern exists. | 570 """Recursively checks if a file/directory matching a pattern exists. |
867 | 571 |
868 Args: | 572 Args: |
869 pattern: Pattern of file or directory name. | 573 pattern: Pattern of file or directory name. |
870 is_file: True if looking for file, or False if looking for directory. | 574 is_file: True if looking for file, or False if looking for directory. |
871 root: Directory to start looking. | 575 root: Directory to start looking. |
872 | 576 |
873 Returns: | 577 Returns: |
874 True, if root contains the directory name pattern, or | 578 True, if root contains the directory name pattern, or |
875 False otherwise. | 579 False otherwise. |
876 """ | 580 """ |
877 for path, dirs, files in os.walk(os.path.abspath(root)): | 581 for path, dirs, files in os.walk(os.path.abspath(root)): |
878 if is_file: | 582 if is_file: |
879 if len(fnmatch.filter(files, pattern)): | 583 if len(fnmatch.filter(files, pattern)): |
880 return True | 584 return True |
881 else: | 585 else: |
882 if len(fnmatch.filter(dirs, pattern)): | 586 if len(fnmatch.filter(dirs, pattern)): |
883 return True | 587 return True |
884 return False | 588 return False |
885 | 589 |
886 def _GetDirectoryPath(self, pattern, root=os.curdir): | |
887 """Get the path of a directory in another directory. | |
888 | |
889 Args: | |
890 pattern: Pattern of directory name. | |
891 root: Directory to start looking. | |
892 | |
893 Returns: | |
894 A string of the path. | |
895 """ | |
896 for path, dirs, files in os.walk(os.path.abspath(root)): | |
897 result = fnmatch.filter(dirs, pattern) | |
898 if result: | |
899 return os.path.join(path, result[0]) | |
900 return None | |
901 | |
902 def _HasAllSystemRequirements(self): | 590 def _HasAllSystemRequirements(self): |
903 """Verify NaCl SDK installation system requirements. | 591 """Verify NaCl SDK installation system requirements. |
904 | 592 |
905 Returns: | 593 Returns: |
906 True, if system passed requirements, or | 594 True, if system passed requirements, or |
907 False otherwise. | 595 False otherwise. |
908 """ | 596 """ |
909 # Check python version. | 597 # Check python version. |
910 if sys.version_info[0:2] < (2, 6): | 598 if sys.version_info[0:2] < (2, 6): |
911 return False | 599 return False |
(...skipping 16 matching lines...) Expand all Loading... | |
928 # NaCl supports Chrome 10 and higher builds. | 616 # NaCl supports Chrome 10 and higher builds. |
929 min_required_chrome_build = self._settings['min_required_chrome_build'] | 617 min_required_chrome_build = self._settings['min_required_chrome_build'] |
930 browser_info = self.GetBrowserInfo() | 618 browser_info = self.GetBrowserInfo() |
931 chrome_version = browser_info['properties']['ChromeVersion'] | 619 chrome_version = browser_info['properties']['ChromeVersion'] |
932 chrome_build = int(chrome_version.split('.')[0]) | 620 chrome_build = int(chrome_version.split('.')[0]) |
933 return chrome_build >= min_required_chrome_build | 621 return chrome_build >= min_required_chrome_build |
934 | 622 |
935 def _DownloadNaClSDK(self): | 623 def _DownloadNaClSDK(self): |
936 """Download NaCl SDK.""" | 624 """Download NaCl SDK.""" |
937 self._temp_dir = tempfile.mkdtemp() | 625 self._temp_dir = tempfile.mkdtemp() |
938 if pyauto.PyUITest.IsWin(): | 626 dl_file = urllib2.urlopen(self._settings['post_sdk_zip']) |
939 dl_file = urllib2.urlopen(self._settings['release_win_sdk_url']) | 627 file_path = os.path.join(self._temp_dir, 'nacl_sdk.zip') |
940 file_path = os.path.join(self._temp_dir, 'naclsdk_win.exe') | |
941 elif pyauto.PyUITest.IsMac(): | |
942 dl_file = urllib2.urlopen(self._settings['release_mac_sdk_url']) | |
943 file_path = os.path.join(self._temp_dir, 'naclsdk_mac.tgz') | |
944 elif pyauto.PyUITest.IsLinux(): | |
945 dl_file = urllib2.urlopen(self._settings['release_lin_sdk_url']) | |
946 file_path = os.path.join(self._temp_dir, 'naclsdk_linux.tgz') | |
947 else: | |
948 self.fail(msg='NaCl SDK does not support this OS.') | |
949 | 628 |
950 try: | 629 try: |
951 f = open(file_path, 'wb') | 630 f = open(file_path, 'wb') |
952 f.write(dl_file.read()) | 631 f.write(dl_file.read()) |
953 except IOError: | 632 except IOError: |
954 self.fail(msg='Cannot open %s.' % file_path) | 633 self.fail(msg='Cannot open %s.' % file_path) |
955 finally: | 634 finally: |
956 f.close() | 635 f.close() |
957 | 636 |
958 def _ExtractNaClSDK(self): | 637 def _ExtractNaClSDK(self): |
959 """Extract NaCl SDK.""" | 638 """Extract NaCl SDK.""" |
960 if pyauto.PyUITest.IsWin(): | 639 source_file = os.path.join(self._temp_dir, 'nacl_sdk.zip') |
961 source_file = os.path.join(self._temp_dir, 'naclsdk_win.exe') | 640 if zipfile.is_zipfile(source_file): |
962 proc = subprocess.Popen([source_file, '/S', '/D=' + | 641 zip = zipfile.ZipFile(source_file, 'r') |
963 self._extracted_sdk_path], | 642 zip.extractall(self._extracted_sdk_path) |
964 stdout=subprocess.PIPE) | |
965 proc.communicate() | |
966 if not os.listdir(self._extracted_sdk_path): | |
967 self.fail(msg='Failed to extract NaCl SDK.') | |
968 elif pyauto.PyUITest.IsMac(): | |
969 source_file = os.path.join(self._temp_dir, 'naclsdk_mac.tgz') | |
970 tar = tarfile.open(source_file, 'r') | |
971 tar.extractall(self._extracted_sdk_path) | |
972 elif pyauto.PyUITest.IsLinux(): | |
973 source_file = os.path.join(self._temp_dir, 'naclsdk_linux.tgz') | |
974 tar = tarfile.open(source_file, 'r') | |
975 tar.extractall(self._extracted_sdk_path) | |
976 else: | 643 else: |
977 self.fail(msg='NaCl SDK does not support this OS.') | 644 self.fail(msg='%s is not a valid zip file' % source_file) |
978 | 645 |
979 def _IsURLAlive(self, url): | 646 def _IsURLAlive(self, url): |
980 """Test if URL is alive.""" | 647 """Test if URL is alive.""" |
981 try: | 648 try: |
982 urllib2.urlopen(url) | 649 urllib2.urlopen(url) |
983 except: | 650 except: |
984 return False | 651 return False |
985 return True | 652 return True |
986 | 653 |
987 def _CloseHTTPServer(self, proc=None): | 654 def _CloseHTTPServer(self, proc=None): |
(...skipping 15 matching lines...) Expand all Loading... | |
1003 lambda: self._IsURLAlive('http://localhost:5103'), | 670 lambda: self._IsURLAlive('http://localhost:5103'), |
1004 timeout=30, retry_sleep=1, expect_retval=False) | 671 timeout=30, retry_sleep=1, expect_retval=False) |
1005 if not success: | 672 if not success: |
1006 if not proc: | 673 if not proc: |
1007 self.fail(msg='Failed to close HTTP server.') | 674 self.fail(msg='Failed to close HTTP server.') |
1008 else: | 675 else: |
1009 if proc.poll() == None: | 676 if proc.poll() == None: |
1010 try: | 677 try: |
1011 proc.kill() | 678 proc.kill() |
1012 except: | 679 except: |
1013 self.fail(msg='Failed to close HTTP server') | 680 self.fail(msg='Failed to close HTTP server.') |
1014 | 681 |
1015 def _SearchNaClSDKTarFile(self, search_list, source_file): | 682 def _SearchNaClSDKFile(self, search_list): |
1016 """Search NaCl SDK tar file for example files and directories. | |
1017 | |
1018 Args: | |
1019 search_list: A list of strings, representing file and | |
1020 directory names for which to search. | |
1021 source_file: The string name of an NaCl SDK tar file. | |
1022 """ | |
1023 tar = tarfile.open(source_file, 'r') | |
1024 | |
1025 # Look for files and directories in examples. | |
1026 files = copy.deepcopy(search_list) | |
1027 for tar_info in tar: | |
1028 file_name = tar_info.name | |
1029 if tar_info.isdir() and not file_name.endswith('/'): | |
1030 file_name = file_name + '/' | |
1031 | |
1032 for name in files: | |
1033 if file_name.find('examples/' + name): | |
1034 files.remove(name) | |
1035 if not files: | |
1036 break | |
1037 | |
1038 tar.close() | |
1039 | |
1040 self.assertEqual(len(files), 0, | |
1041 msg='Missing files or directories: %s' % | |
1042 ', '.join(map(str, files))) | |
1043 | |
1044 def _SearchNaClSDKFileWindows(self, search_list): | |
1045 """Search NaCl SDK file for example files and directories in Windows. | 683 """Search NaCl SDK file for example files and directories in Windows. |
1046 | 684 |
1047 Args: | 685 Args: |
1048 search_list: A list of strings, representing file and | 686 search_list: A list of strings, representing file and |
1049 directory names for which to search. | 687 directory names for which to search. |
1050 """ | 688 """ |
1051 missing_items = [] | 689 missing_items = [] |
1052 for name in search_list: | 690 for name in search_list: |
1053 is_file = name.find('/') < 0 | 691 is_file = name.find('/') < 0 |
1054 if not is_file: | 692 if not is_file: |
1055 name = name.replace('/', '') | 693 name = name.replace('/', '') |
1056 if not self._HasPathInTree(name, is_file, self._extracted_sdk_path): | 694 if not self._HasPathInTree(name, is_file, self._extracted_sdk_path): |
1057 missing_items.append(name) | 695 missing_items.append(name) |
1058 self.assertEqual(len(missing_items), 0, | 696 self.assertEqual(len(missing_items), 0, |
1059 msg='Missing files or directories: %s' % | 697 msg='Missing files or directories: %s' % |
1060 ', '.join(map(str, missing_items))) | 698 ', '.join(map(str, missing_items))) |
1061 | 699 |
1062 def _EnableNaClPlugin(self): | 700 def ExtraChromeFlags(self): |
1063 """Enable NaCl plugin.""" | 701 """Ensures Nacl is enabled. |
1064 nacl_plugin = (self.GetPluginsInfo().PluginForName('Chrome NaCl') or | |
1065 self.GetPluginsInfo().PluginForName('Native Client')) | |
1066 if not nacl_plugin: | |
1067 self.fail(msg='No NaCl plugin found.') | |
1068 self.EnablePlugin(nacl_plugin[0]['path']) | |
1069 | 702 |
1070 self.NavigateToURL('about:flags') | 703 Returns: |
1071 | 704 A list of extra flags to pass to Chrome when it is launched. |
1072 js_code = """ | |
1073 chrome.send('enableFlagsExperiment', ['enable-nacl', 'true']); | |
1074 requestFlagsExperimentsData(); | |
1075 window.domAutomationController.send('done'); | |
1076 """ | 705 """ |
1077 self.ExecuteJavascript(js_code) | 706 extra_chrome_flags = [ |
1078 self.RestartBrowser(False) | 707 '--enable-nacl', |
1079 | 708 '--enable-nacl-exception-handling', |
1080 self.NavigateToURL('about://version') | 709 '--nacl-gdb', |
1081 self.assertEqual(self.FindInPage('--enable-nacl')['match_count'], 1, | 710 ] |
Nirnimesh
2012/05/11 09:13:32
indent by 2 spaces to the left
pnihalani1
2012/05/11 17:20:08
Done.
| |
1082 msg='Missing expected Chrome flag --enable-nacl.') | 711 return pyauto.PyUITest.ExtraChromeFlags(self) + extra_chrome_flags |
1083 | |
1084 | 712 |
1085 if __name__ == '__main__': | 713 if __name__ == '__main__': |
1086 pyauto_functional.Main() | 714 pyauto_functional.Main() |
OLD | NEW |