Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 1 | # Copyright (c) 2013 The Chromium OS 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 | |
Mike Frysinger | eb753bf | 2013-11-22 16:05:35 -0500 | [diff] [blame] | 5 | """Unittests for upload_symbols.py""" |
| 6 | |
Mike Frysinger | 7f9be14 | 2014-01-15 02:16:42 -0500 | [diff] [blame] | 7 | from __future__ import print_function |
| 8 | |
Mike Frysinger | 0a2fd92 | 2014-09-12 20:23:42 -0700 | [diff] [blame] | 9 | import BaseHTTPServer |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 10 | import ctypes |
Mike Frysinger | 06da520 | 2014-09-26 17:30:33 -0500 | [diff] [blame] | 11 | import errno |
Mike Frysinger | ea838d1 | 2014-12-08 11:55:32 -0500 | [diff] [blame] | 12 | import mock |
Mike Frysinger | 0c0efa2 | 2014-02-09 23:32:23 -0500 | [diff] [blame] | 13 | import multiprocessing |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 14 | import os |
Mike Frysinger | 0a2fd92 | 2014-09-12 20:23:42 -0700 | [diff] [blame] | 15 | import signal |
Mike Frysinger | 06da520 | 2014-09-26 17:30:33 -0500 | [diff] [blame] | 16 | import socket |
Mike Frysinger | 0a2fd92 | 2014-09-12 20:23:42 -0700 | [diff] [blame] | 17 | import SocketServer |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 18 | import sys |
Aviv Keshet | d1f0463 | 2014-05-09 11:33:46 -0700 | [diff] [blame] | 19 | import time |
Mike Frysinger | 094a217 | 2013-08-14 12:54:35 -0400 | [diff] [blame] | 20 | import urllib2 |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 21 | |
Mike Frysinger | 079863c | 2014-10-09 23:16:46 -0400 | [diff] [blame] | 22 | # We specifically set up a local server to connect to, so make sure we |
| 23 | # delete any proxy settings that might screw that up. We also need to |
| 24 | # do it here because modules that are imported below will implicitly |
| 25 | # initialize with this proxy setting rather than dynamically pull it |
| 26 | # on the fly :(. |
| 27 | os.environ.pop('http_proxy', None) |
| 28 | |
Mike Frysinger | 02e9240 | 2013-11-22 16:22:02 -0500 | [diff] [blame] | 29 | from chromite.lib import cros_build_lib |
Ralph Nathan | 446aee9 | 2015-03-23 14:44:56 -0700 | [diff] [blame^] | 30 | from chromite.lib import cros_logging as logging |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 31 | from chromite.lib import cros_test_lib |
| 32 | from chromite.lib import osutils |
| 33 | from chromite.lib import parallel |
| 34 | from chromite.lib import parallel_unittest |
Mike Frysinger | 0a2fd92 | 2014-09-12 20:23:42 -0700 | [diff] [blame] | 35 | from chromite.lib import remote_access |
Mike Frysinger | 5e30a4b | 2014-02-12 20:23:04 -0500 | [diff] [blame] | 36 | from chromite.scripts import cros_generate_breakpad_symbols |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 37 | from chromite.scripts import upload_symbols |
| 38 | |
Mike Frysinger | 0c0efa2 | 2014-02-09 23:32:23 -0500 | [diff] [blame] | 39 | import isolateserver |
Mike Frysinger | 0c0efa2 | 2014-02-09 23:32:23 -0500 | [diff] [blame] | 40 | |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 41 | |
Mike Frysinger | 0a2fd92 | 2014-09-12 20:23:42 -0700 | [diff] [blame] | 42 | class SymbolServerRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): |
| 43 | """HTTP handler for symbol POSTs""" |
| 44 | |
| 45 | RESP_CODE = None |
| 46 | RESP_MSG = None |
| 47 | |
| 48 | def do_POST(self): |
| 49 | """Handle a POST request""" |
| 50 | # Drain the data from the client. If we don't, we might write the response |
| 51 | # and close the socket before the client finishes, so they die with EPIPE. |
| 52 | clen = int(self.headers.get('Content-Length', '0')) |
| 53 | self.rfile.read(clen) |
| 54 | |
| 55 | self.send_response(self.RESP_CODE, self.RESP_MSG) |
| 56 | self.end_headers() |
| 57 | |
| 58 | def log_message(self, *args, **kwargs): |
| 59 | """Stub the logger as it writes to stderr""" |
| 60 | pass |
| 61 | |
| 62 | |
| 63 | class SymbolServer(SocketServer.ThreadingTCPServer, BaseHTTPServer.HTTPServer): |
| 64 | """Simple HTTP server that forks each request""" |
| 65 | |
| 66 | |
| 67 | class UploadSymbolsServerTest(cros_test_lib.MockTempDirTestCase): |
| 68 | """Tests for UploadSymbols() and a local HTTP server""" |
| 69 | |
| 70 | SYM_CONTENTS = """MODULE Linux arm 123-456 blkid |
| 71 | PUBLIC 1471 0 main""" |
| 72 | |
| 73 | def SpawnServer(self, RequestHandler): |
| 74 | """Spawn a new http server""" |
Mike Frysinger | 06da520 | 2014-09-26 17:30:33 -0500 | [diff] [blame] | 75 | while True: |
| 76 | try: |
| 77 | port = remote_access.GetUnusedPort() |
| 78 | address = ('', port) |
| 79 | self.httpd = SymbolServer(address, RequestHandler) |
| 80 | break |
| 81 | except socket.error as e: |
| 82 | if e.errno == errno.EADDRINUSE: |
| 83 | continue |
| 84 | raise |
Mike Frysinger | 0a2fd92 | 2014-09-12 20:23:42 -0700 | [diff] [blame] | 85 | self.server = 'http://localhost:%i' % port |
Mike Frysinger | 0a2fd92 | 2014-09-12 20:23:42 -0700 | [diff] [blame] | 86 | self.httpd_pid = os.fork() |
| 87 | if self.httpd_pid == 0: |
| 88 | self.httpd.serve_forever(poll_interval=0.1) |
| 89 | sys.exit(0) |
| 90 | |
| 91 | def setUp(self): |
| 92 | self.httpd_pid = None |
| 93 | self.httpd = None |
| 94 | self.server = None |
| 95 | self.sym_file = os.path.join(self.tempdir, 'test.sym') |
| 96 | osutils.WriteFile(self.sym_file, self.SYM_CONTENTS) |
| 97 | |
| 98 | def tearDown(self): |
| 99 | # Only kill the server if we forked one. |
| 100 | if self.httpd_pid: |
| 101 | os.kill(self.httpd_pid, signal.SIGUSR1) |
| 102 | |
| 103 | def testSuccess(self): |
| 104 | """The server returns success for all uploads""" |
| 105 | class Handler(SymbolServerRequestHandler): |
| 106 | """Always return 200""" |
| 107 | RESP_CODE = 200 |
| 108 | |
| 109 | self.SpawnServer(Handler) |
| 110 | ret = upload_symbols.UploadSymbols('', server=self.server, sleep=0, |
| 111 | sym_paths=[self.sym_file] * 10, |
| 112 | retry=False) |
| 113 | self.assertEqual(ret, 0) |
| 114 | |
| 115 | def testError(self): |
| 116 | """The server returns errors for all uploads""" |
| 117 | class Handler(SymbolServerRequestHandler): |
| 118 | """Always return 500""" |
| 119 | RESP_CODE = 500 |
| 120 | RESP_MSG = 'Internal Server Error' |
| 121 | |
| 122 | self.SpawnServer(Handler) |
| 123 | ret = upload_symbols.UploadSymbols('', server=self.server, sleep=0, |
| 124 | sym_paths=[self.sym_file] * 10, |
| 125 | retry=False) |
| 126 | self.assertEqual(ret, 4) |
| 127 | |
| 128 | def testHungServer(self): |
| 129 | """The server chokes, but we recover""" |
| 130 | class Handler(SymbolServerRequestHandler): |
| 131 | """All connections choke forever""" |
| 132 | def do_POST(self): |
| 133 | while True: |
| 134 | time.sleep(1000) |
| 135 | |
| 136 | self.SpawnServer(Handler) |
| 137 | with mock.patch.object(upload_symbols, 'GetUploadTimeout') as m: |
| 138 | m.return_value = 0.1 |
| 139 | ret = upload_symbols.UploadSymbols('', server=self.server, sleep=0, |
| 140 | sym_paths=[self.sym_file] * 10, |
| 141 | retry=False) |
| 142 | self.assertEqual(ret, 4) |
| 143 | |
| 144 | |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 145 | class UploadSymbolsTest(cros_test_lib.MockTempDirTestCase): |
Mike Frysinger | c4ab578 | 2013-10-02 18:14:22 -0400 | [diff] [blame] | 146 | """Tests for UploadSymbols()""" |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 147 | |
| 148 | def setUp(self): |
| 149 | for d in ('foo', 'bar', 'some/dir/here'): |
| 150 | d = os.path.join(self.tempdir, d) |
| 151 | osutils.SafeMakedirs(d) |
| 152 | for f in ('ignored', 'real.sym', 'no.sym.here'): |
| 153 | f = os.path.join(d, f) |
| 154 | osutils.Touch(f) |
Mike Frysinger | d41938e | 2014-02-10 06:37:55 -0500 | [diff] [blame] | 155 | self.sym_paths = [ |
| 156 | 'bar/real.sym', |
| 157 | 'foo/real.sym', |
| 158 | 'some/dir/here/real.sym', |
| 159 | ] |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 160 | |
Mike Frysinger | 5e30a4b | 2014-02-12 20:23:04 -0500 | [diff] [blame] | 161 | self.upload_mock = self.PatchObject(upload_symbols, 'UploadSymbol') |
| 162 | self.PatchObject(cros_generate_breakpad_symbols, 'ReadSymsHeader', |
| 163 | return_value=cros_generate_breakpad_symbols.SymbolHeader( |
| 164 | os='os', cpu='cpu', id='id', name='name')) |
| 165 | |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 166 | def _testUploadURL(self, official, expected_url): |
| 167 | """Helper for checking the url used""" |
Mike Frysinger | 5e30a4b | 2014-02-12 20:23:04 -0500 | [diff] [blame] | 168 | self.upload_mock.return_value = 0 |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 169 | with parallel_unittest.ParallelMock(): |
Mike Frysinger | 02e9240 | 2013-11-22 16:22:02 -0500 | [diff] [blame] | 170 | ret = upload_symbols.UploadSymbols('', official=official, retry=False, |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 171 | breakpad_dir=self.tempdir, sleep=0) |
| 172 | self.assertEqual(ret, 0) |
Mike Frysinger | 5e30a4b | 2014-02-12 20:23:04 -0500 | [diff] [blame] | 173 | self.assertEqual(self.upload_mock.call_count, 3) |
| 174 | for call_args in self.upload_mock.call_args_list: |
Mike Frysinger | 0c0efa2 | 2014-02-09 23:32:23 -0500 | [diff] [blame] | 175 | url, sym_item = call_args[0] |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 176 | self.assertEqual(url, expected_url) |
Mike Frysinger | 0c0efa2 | 2014-02-09 23:32:23 -0500 | [diff] [blame] | 177 | self.assertTrue(sym_item.sym_file.endswith('.sym')) |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 178 | |
| 179 | def testOfficialUploadURL(self): |
| 180 | """Verify we upload to the real crash server for official builds""" |
| 181 | self._testUploadURL(True, upload_symbols.OFFICIAL_UPLOAD_URL) |
| 182 | |
| 183 | def testUnofficialUploadURL(self): |
| 184 | """Verify we upload to the staging crash server for unofficial builds""" |
| 185 | self._testUploadURL(False, upload_symbols.STAGING_UPLOAD_URL) |
| 186 | |
| 187 | def testUploadSymbolFailureSimple(self): |
| 188 | """Verify that when UploadSymbol fails, the error count is passed up""" |
| 189 | def UploadSymbol(*_args, **kwargs): |
| 190 | kwargs['num_errors'].value = 4 |
Mike Frysinger | 5e30a4b | 2014-02-12 20:23:04 -0500 | [diff] [blame] | 191 | self.upload_mock.side_effect = UploadSymbol |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 192 | with parallel_unittest.ParallelMock(): |
Mike Frysinger | 02e9240 | 2013-11-22 16:22:02 -0500 | [diff] [blame] | 193 | ret = upload_symbols.UploadSymbols('', breakpad_dir=self.tempdir, sleep=0, |
| 194 | retry=False) |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 195 | self.assertEquals(ret, 4) |
| 196 | |
| 197 | def testUploadCount(self): |
| 198 | """Verify we can limit the number of uploaded symbols""" |
Mike Frysinger | 5e30a4b | 2014-02-12 20:23:04 -0500 | [diff] [blame] | 199 | self.upload_mock.return_value = 0 |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 200 | for c in xrange(3): |
Mike Frysinger | 5e30a4b | 2014-02-12 20:23:04 -0500 | [diff] [blame] | 201 | self.upload_mock.reset_mock() |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 202 | with parallel_unittest.ParallelMock(): |
| 203 | ret = upload_symbols.UploadSymbols('', breakpad_dir=self.tempdir, |
Mike Frysinger | 8ec8c50 | 2014-02-10 00:19:13 -0500 | [diff] [blame] | 204 | sleep=0, upload_limit=c) |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 205 | self.assertEquals(ret, 0) |
Mike Frysinger | 5e30a4b | 2014-02-12 20:23:04 -0500 | [diff] [blame] | 206 | self.assertEqual(self.upload_mock.call_count, c) |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 207 | |
Mike Frysinger | 7f9be14 | 2014-01-15 02:16:42 -0500 | [diff] [blame] | 208 | def testFailedFileList(self): |
| 209 | """Verify the failed file list is populated with the right content""" |
| 210 | def UploadSymbol(*args, **kwargs): |
Mike Frysinger | 0c0efa2 | 2014-02-09 23:32:23 -0500 | [diff] [blame] | 211 | kwargs['failed_queue'].put(args[1].sym_file) |
Mike Frysinger | 7f9be14 | 2014-01-15 02:16:42 -0500 | [diff] [blame] | 212 | kwargs['num_errors'].value = 4 |
Mike Frysinger | 5e30a4b | 2014-02-12 20:23:04 -0500 | [diff] [blame] | 213 | self.upload_mock.side_effect = UploadSymbol |
Mike Frysinger | 7f9be14 | 2014-01-15 02:16:42 -0500 | [diff] [blame] | 214 | with parallel_unittest.ParallelMock(): |
| 215 | failed_list = os.path.join(self.tempdir, 'list') |
| 216 | ret = upload_symbols.UploadSymbols('', breakpad_dir=self.tempdir, sleep=0, |
| 217 | retry=False, failed_list=failed_list) |
| 218 | self.assertEquals(ret, 4) |
| 219 | |
| 220 | # Need to sort the output as parallel/fs discovery can be unordered. |
| 221 | got_list = sorted(osutils.ReadFile(failed_list).splitlines()) |
Mike Frysinger | d41938e | 2014-02-10 06:37:55 -0500 | [diff] [blame] | 222 | self.assertEquals(self.sym_paths, got_list) |
| 223 | |
| 224 | def _testUpload(self, inputs, sym_paths=None): |
| 225 | """Helper for testing uploading of specific paths""" |
| 226 | if sym_paths is None: |
| 227 | sym_paths = inputs |
| 228 | |
| 229 | self.upload_mock.return_value = 0 |
| 230 | with parallel_unittest.ParallelMock(): |
| 231 | ret = upload_symbols.UploadSymbols(sym_paths=inputs, sleep=0, |
| 232 | retry=False) |
| 233 | self.assertEquals(ret, 0) |
| 234 | self.assertEquals(self.upload_mock.call_count, len(sym_paths)) |
| 235 | |
| 236 | # Since upload order is arbitrary, we have to do a manual scan for each |
| 237 | # path ourselves against the uploaded file list. |
| 238 | found_syms = [x[0][1].sym_file for x in self.upload_mock.call_args_list] |
| 239 | for found_sym in found_syms: |
| 240 | for path in sym_paths: |
| 241 | if found_sym.endswith(path): |
| 242 | break |
| 243 | else: |
| 244 | raise AssertionError('Could not locate %s in %r' % (path, found_syms)) |
| 245 | |
| 246 | def testUploadFiles(self): |
| 247 | """Test uploading specific symbol files""" |
| 248 | sym_paths = ( |
| 249 | os.path.join(self.tempdir, 'bar', 'real.sym'), |
| 250 | os.path.join(self.tempdir, 'foo', 'real.sym'), |
| 251 | ) |
| 252 | self._testUpload(sym_paths) |
| 253 | |
| 254 | def testUploadDirectory(self): |
| 255 | """Test uploading directory of symbol files""" |
| 256 | self._testUpload([self.tempdir], sym_paths=self.sym_paths) |
| 257 | |
| 258 | def testUploadLocalTarball(self): |
| 259 | """Test uploading symbols contains in a local tarball""" |
| 260 | tarball = os.path.join(self.tempdir, 'syms.tar.gz') |
| 261 | cros_build_lib.CreateTarball( |
| 262 | 'syms.tar.gz', self.tempdir, compression=cros_build_lib.COMP_GZIP, |
| 263 | inputs=('foo', 'bar', 'some')) |
| 264 | self._testUpload([tarball], sym_paths=self.sym_paths) |
| 265 | |
| 266 | def testUploadRemoteTarball(self): |
| 267 | """Test uploading symbols contains in a remote tarball""" |
| 268 | # TODO: Need to figure out how to mock out lib.cache.TarballCache. |
Mike Frysinger | 7f9be14 | 2014-01-15 02:16:42 -0500 | [diff] [blame] | 269 | |
Mike Frysinger | 58312e9 | 2014-03-18 04:18:36 -0400 | [diff] [blame] | 270 | def testDedupeNotifyFailure(self): |
| 271 | """Test that a dedupe server failure midway doesn't wedge things""" |
| 272 | api_mock = mock.MagicMock() |
| 273 | |
| 274 | def _Contains(items): |
| 275 | """Do not dedupe anything""" |
| 276 | return items |
| 277 | api_mock.contains.side_effect = _Contains |
| 278 | |
| 279 | # Use a list so the closure below can modify the value. |
| 280 | item_count = [0] |
| 281 | # Pick a number big enough to trigger a hang normally, but not so |
| 282 | # big it adds a lot of overhead. |
| 283 | item_limit = 50 |
| 284 | def _Push(*_args): |
| 285 | """Die in the middle of the push list""" |
| 286 | item_count[0] += 1 |
| 287 | if item_count[0] > (item_limit / 10): |
| 288 | raise ValueError('time to die') |
| 289 | api_mock.push.side_effect = _Push |
| 290 | |
| 291 | self.PatchObject(isolateserver, 'get_storage_api', return_value=api_mock) |
| 292 | |
| 293 | def _Uploader(*args, **kwargs): |
| 294 | """Pass the uploaded symbol to the deduper""" |
| 295 | sym_item = args[1] |
| 296 | passed_queue = kwargs['passed_queue'] |
| 297 | passed_queue.put(sym_item) |
| 298 | self.upload_mock.side_effect = _Uploader |
| 299 | |
| 300 | self.upload_mock.return_value = 0 |
| 301 | with parallel_unittest.ParallelMock(): |
| 302 | ret = upload_symbols.UploadSymbols( |
| 303 | '', sym_paths=[self.tempdir] * item_limit, sleep=0, |
| 304 | dedupe_namespace='inva!id name$pace') |
| 305 | self.assertEqual(ret, 0) |
| 306 | # This test normally passes by not hanging. |
| 307 | |
Aviv Keshet | d1f0463 | 2014-05-09 11:33:46 -0700 | [diff] [blame] | 308 | def testSlowDedupeSystem(self): |
| 309 | """Verify a slow-to-join process doesn't break things when dedupe is off""" |
| 310 | # The sleep value here is inherently a little racy, but seems to be good |
| 311 | # enough to trigger the bug on a semi-regular basis on developer systems. |
| 312 | self.PatchObject(upload_symbols, 'SymbolDeduplicatorNotify', |
| 313 | side_effect=lambda *args: time.sleep(1)) |
| 314 | # Test passing means the code didn't throw an exception. |
| 315 | upload_symbols.UploadSymbols(sym_paths=[self.tempdir]) |
| 316 | |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 317 | |
Mike Frysinger | 0c0efa2 | 2014-02-09 23:32:23 -0500 | [diff] [blame] | 318 | class SymbolDeduplicatorNotifyTest(cros_test_lib.MockTestCase): |
| 319 | """Tests for SymbolDeduplicatorNotify()""" |
| 320 | |
| 321 | def setUp(self): |
| 322 | self.storage_mock = self.PatchObject(isolateserver, 'get_storage_api') |
| 323 | |
| 324 | def testSmoke(self): |
| 325 | """Basic run through the system.""" |
| 326 | q = mock.MagicMock() |
| 327 | q.get.side_effect = (upload_symbols.FakeItem(), None,) |
| 328 | upload_symbols.SymbolDeduplicatorNotify('name', q) |
| 329 | |
| 330 | def testStorageException(self): |
| 331 | """We want to just warn & move on when dedupe server fails""" |
Ralph Nathan | 446aee9 | 2015-03-23 14:44:56 -0700 | [diff] [blame^] | 332 | log_mock = self.PatchObject(logging, 'warning') |
Mike Frysinger | 0c0efa2 | 2014-02-09 23:32:23 -0500 | [diff] [blame] | 333 | q = mock.MagicMock() |
| 334 | q.get.side_effect = (upload_symbols.FakeItem(), None,) |
| 335 | self.storage_mock.side_effect = Exception |
| 336 | upload_symbols.SymbolDeduplicatorNotify('name', q) |
| 337 | self.assertEqual(log_mock.call_count, 1) |
| 338 | |
| 339 | |
| 340 | class SymbolDeduplicatorTest(cros_test_lib.MockTestCase): |
| 341 | """Tests for SymbolDeduplicator()""" |
| 342 | |
| 343 | def setUp(self): |
| 344 | self.storage_mock = mock.MagicMock() |
| 345 | self.header_mock = self.PatchObject( |
| 346 | cros_generate_breakpad_symbols, 'ReadSymsHeader', |
| 347 | return_value=cros_generate_breakpad_symbols.SymbolHeader( |
| 348 | os='os', cpu='cpu', id='id', name='name')) |
| 349 | |
| 350 | def testNoStorageOrPaths(self): |
| 351 | """We don't want to talk to the server if there's no storage or files""" |
| 352 | upload_symbols.SymbolDeduplicator(None, []) |
| 353 | upload_symbols.SymbolDeduplicator(self.storage_mock, []) |
| 354 | self.assertEqual(self.storage_mock.call_count, 0) |
| 355 | self.assertEqual(self.header_mock.call_count, 0) |
| 356 | |
| 357 | def testStorageException(self): |
| 358 | """We want to just warn & move on when dedupe server fails""" |
Ralph Nathan | 446aee9 | 2015-03-23 14:44:56 -0700 | [diff] [blame^] | 359 | log_mock = self.PatchObject(logging, 'warning') |
Mike Frysinger | 0c0efa2 | 2014-02-09 23:32:23 -0500 | [diff] [blame] | 360 | self.storage_mock.contains.side_effect = Exception('storage error') |
| 361 | sym_paths = ['/a', '/bbbbbb', '/cc.c'] |
| 362 | ret = upload_symbols.SymbolDeduplicator(self.storage_mock, sym_paths) |
| 363 | self.assertEqual(log_mock.call_count, 1) |
| 364 | self.assertEqual(self.storage_mock.contains.call_count, 1) |
| 365 | self.assertEqual(self.header_mock.call_count, len(sym_paths)) |
| 366 | self.assertEqual(len(ret), len(sym_paths)) |
| 367 | |
| 368 | |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 369 | class UploadSymbolTest(cros_test_lib.MockTempDirTestCase): |
Mike Frysinger | c4ab578 | 2013-10-02 18:14:22 -0400 | [diff] [blame] | 370 | """Tests for UploadSymbol()""" |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 371 | |
| 372 | def setUp(self): |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 373 | self.sym_file = os.path.join(self.tempdir, 'foo.sym') |
Mike Frysinger | 0c0efa2 | 2014-02-09 23:32:23 -0500 | [diff] [blame] | 374 | self.sym_item = upload_symbols.FakeItem(sym_file=self.sym_file) |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 375 | self.url = 'http://eatit' |
Mike Frysinger | 5e30a4b | 2014-02-12 20:23:04 -0500 | [diff] [blame] | 376 | self.upload_mock = self.PatchObject(upload_symbols, 'SymUpload') |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 377 | |
| 378 | def testUploadSymbolNormal(self): |
| 379 | """Verify we try to upload on a normal file""" |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 380 | osutils.Touch(self.sym_file) |
Mike Frysinger | 0c0efa2 | 2014-02-09 23:32:23 -0500 | [diff] [blame] | 381 | ret = upload_symbols.UploadSymbol(self.url, self.sym_item) |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 382 | self.assertEqual(ret, 0) |
Mike Frysinger | 0c0efa2 | 2014-02-09 23:32:23 -0500 | [diff] [blame] | 383 | self.upload_mock.assert_called_with(self.url, self.sym_item) |
Mike Frysinger | 5e30a4b | 2014-02-12 20:23:04 -0500 | [diff] [blame] | 384 | self.assertEqual(self.upload_mock.call_count, 1) |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 385 | |
| 386 | def testUploadSymbolErrorCountExceeded(self): |
| 387 | """Verify that when the error count gets too high, we stop uploading""" |
| 388 | errors = ctypes.c_int(10000) |
| 389 | # Pass in garbage values so that we crash if num_errors isn't handled. |
Mike Frysinger | 0c0efa2 | 2014-02-09 23:32:23 -0500 | [diff] [blame] | 390 | ret = upload_symbols.UploadSymbol(None, self.sym_item, sleep=None, |
| 391 | num_errors=errors) |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 392 | self.assertEqual(ret, 0) |
| 393 | |
Mike Frysinger | c4ab578 | 2013-10-02 18:14:22 -0400 | [diff] [blame] | 394 | def testUploadRetryErrors(self, side_effect=None): |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 395 | """Verify that we retry errors (and eventually give up)""" |
Mike Frysinger | c4ab578 | 2013-10-02 18:14:22 -0400 | [diff] [blame] | 396 | if not side_effect: |
| 397 | side_effect = urllib2.HTTPError('http://', 400, 'fail', {}, None) |
Mike Frysinger | 5e30a4b | 2014-02-12 20:23:04 -0500 | [diff] [blame] | 398 | self.upload_mock.side_effect = side_effect |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 399 | errors = ctypes.c_int() |
Mike Frysinger | 0c0efa2 | 2014-02-09 23:32:23 -0500 | [diff] [blame] | 400 | item = upload_symbols.FakeItem(sym_file='/dev/null') |
| 401 | ret = upload_symbols.UploadSymbol(self.url, item, num_errors=errors) |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 402 | self.assertEqual(ret, 1) |
Mike Frysinger | 0c0efa2 | 2014-02-09 23:32:23 -0500 | [diff] [blame] | 403 | self.upload_mock.assert_called_with(self.url, item) |
Mike Frysinger | 5e30a4b | 2014-02-12 20:23:04 -0500 | [diff] [blame] | 404 | self.assertTrue(self.upload_mock.call_count >= upload_symbols.MAX_RETRIES) |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 405 | |
Mike Frysinger | c4ab578 | 2013-10-02 18:14:22 -0400 | [diff] [blame] | 406 | def testConnectRetryErrors(self): |
| 407 | """Verify that we retry errors (and eventually give up) w/connect errors""" |
| 408 | side_effect = urllib2.URLError('foo') |
| 409 | self.testUploadRetryErrors(side_effect=side_effect) |
| 410 | |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 411 | def testTruncateTooBigFiles(self): |
| 412 | """Verify we shrink big files""" |
Mike Frysinger | 0c0efa2 | 2014-02-09 23:32:23 -0500 | [diff] [blame] | 413 | def SymUpload(_url, sym_item): |
| 414 | content = osutils.ReadFile(sym_item.sym_file) |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 415 | self.assertEqual(content, 'some junk\n') |
Mike Frysinger | 5e30a4b | 2014-02-12 20:23:04 -0500 | [diff] [blame] | 416 | self.upload_mock.upload_mock.side_effect = SymUpload |
| 417 | content = '\n'.join(( |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 418 | 'STACK CFI 1234', |
| 419 | 'some junk', |
| 420 | 'STACK CFI 1234', |
Mike Frysinger | 5e30a4b | 2014-02-12 20:23:04 -0500 | [diff] [blame] | 421 | )) |
| 422 | osutils.WriteFile(self.sym_file, content) |
Mike Frysinger | 0c0efa2 | 2014-02-09 23:32:23 -0500 | [diff] [blame] | 423 | ret = upload_symbols.UploadSymbol(self.url, self.sym_item, file_limit=1) |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 424 | self.assertEqual(ret, 0) |
Mike Frysinger | 0c0efa2 | 2014-02-09 23:32:23 -0500 | [diff] [blame] | 425 | # Make sure the item passed to the upload has a temp file and not the |
| 426 | # original -- only the temp one has been stripped down. |
| 427 | temp_item = self.upload_mock.call_args[0][1] |
| 428 | self.assertNotEqual(temp_item.sym_file, self.sym_item.sym_file) |
Mike Frysinger | 5e30a4b | 2014-02-12 20:23:04 -0500 | [diff] [blame] | 429 | self.assertEqual(self.upload_mock.call_count, 1) |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 430 | |
| 431 | def testTruncateReallyLargeFiles(self): |
| 432 | """Verify we try to shrink really big files""" |
Mike Frysinger | 02e9240 | 2013-11-22 16:22:02 -0500 | [diff] [blame] | 433 | warn_mock = self.PatchObject(cros_build_lib, 'PrintBuildbotStepWarnings') |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 434 | with open(self.sym_file, 'w+b') as f: |
| 435 | f.truncate(upload_symbols.CRASH_SERVER_FILE_LIMIT + 100) |
| 436 | f.seek(0) |
| 437 | f.write('STACK CFI 1234\n\n') |
Mike Frysinger | 0c0efa2 | 2014-02-09 23:32:23 -0500 | [diff] [blame] | 438 | ret = upload_symbols.UploadSymbol(self.url, self.sym_item) |
Mike Frysinger | 02e9240 | 2013-11-22 16:22:02 -0500 | [diff] [blame] | 439 | self.assertEqual(ret, 0) |
Mike Frysinger | 0c0efa2 | 2014-02-09 23:32:23 -0500 | [diff] [blame] | 440 | # Make sure the item passed to the upload has a temp file and not the |
| 441 | # original -- only the temp one has been truncated. |
| 442 | temp_item = self.upload_mock.call_args[0][1] |
| 443 | self.assertNotEqual(temp_item.sym_file, self.sym_item.sym_file) |
Mike Frysinger | 5e30a4b | 2014-02-12 20:23:04 -0500 | [diff] [blame] | 444 | self.assertEqual(self.upload_mock.call_count, 1) |
Mike Frysinger | 02e9240 | 2013-11-22 16:22:02 -0500 | [diff] [blame] | 445 | self.assertEqual(warn_mock.call_count, 1) |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 446 | |
| 447 | |
Mike Frysinger | 094a217 | 2013-08-14 12:54:35 -0400 | [diff] [blame] | 448 | class SymUploadTest(cros_test_lib.MockTempDirTestCase): |
Mike Frysinger | c4ab578 | 2013-10-02 18:14:22 -0400 | [diff] [blame] | 449 | """Tests for SymUpload()""" |
Mike Frysinger | 094a217 | 2013-08-14 12:54:35 -0400 | [diff] [blame] | 450 | |
| 451 | SYM_URL = 'http://localhost/post/it/here' |
| 452 | SYM_CONTENTS = """MODULE Linux arm 123-456 blkid |
| 453 | PUBLIC 1471 0 main""" |
| 454 | |
| 455 | def setUp(self): |
| 456 | self.sym_file = os.path.join(self.tempdir, 'test.sym') |
| 457 | osutils.WriteFile(self.sym_file, self.SYM_CONTENTS) |
Mike Frysinger | 0c0efa2 | 2014-02-09 23:32:23 -0500 | [diff] [blame] | 458 | self.sym_item = upload_symbols.SymbolItem(self.sym_file) |
Mike Frysinger | 094a217 | 2013-08-14 12:54:35 -0400 | [diff] [blame] | 459 | |
| 460 | def testPostUpload(self): |
| 461 | """Verify HTTP POST has all the fields we need""" |
| 462 | m = self.PatchObject(urllib2, 'urlopen', autospec=True) |
Mike Frysinger | 0c0efa2 | 2014-02-09 23:32:23 -0500 | [diff] [blame] | 463 | upload_symbols.SymUpload(self.SYM_URL, self.sym_item) |
Mike Frysinger | 094a217 | 2013-08-14 12:54:35 -0400 | [diff] [blame] | 464 | self.assertEquals(m.call_count, 1) |
| 465 | req = m.call_args[0][0] |
| 466 | self.assertEquals(req.get_full_url(), self.SYM_URL) |
| 467 | data = ''.join([x for x in req.get_data()]) |
| 468 | |
| 469 | fields = { |
| 470 | 'code_file': 'blkid', |
| 471 | 'debug_file': 'blkid', |
| 472 | 'debug_identifier': '123456', |
| 473 | 'os': 'Linux', |
| 474 | 'cpu': 'arm', |
| 475 | } |
| 476 | for key, val in fields.iteritems(): |
| 477 | line = 'Content-Disposition: form-data; name="%s"\r\n' % key |
| 478 | self.assertTrue(line in data) |
| 479 | line = '%s\r\n' % val |
| 480 | self.assertTrue(line in data) |
| 481 | line = ('Content-Disposition: form-data; name="symbol_file"; ' |
| 482 | 'filename="test.sym"\r\n') |
| 483 | self.assertTrue(line in data) |
| 484 | self.assertTrue(self.SYM_CONTENTS in data) |
| 485 | |
Mike Frysinger | 7104666 | 2014-09-12 18:15:15 -0700 | [diff] [blame] | 486 | def testTimeout(self): |
| 487 | """Verify timeouts scale based on filesize""" |
| 488 | m = self.PatchObject(urllib2, 'urlopen', autospec=True) |
| 489 | size = self.PatchObject(os.path, 'getsize') |
| 490 | |
| 491 | tests = ( |
| 492 | # Small files should get rounded up to the minimum timeout. |
| 493 | (10, upload_symbols.UPLOAD_MIN_TIMEOUT), |
| 494 | # A 50MiB file should take like ~4 minutes. |
| 495 | (50 * 1024 * 1024, 257), |
| 496 | ) |
| 497 | for size.return_value, timeout in tests: |
| 498 | upload_symbols.SymUpload(self.SYM_URL, self.sym_item) |
| 499 | self.assertEqual(m.call_args[1]['timeout'], timeout) |
| 500 | |
Mike Frysinger | 094a217 | 2013-08-14 12:54:35 -0400 | [diff] [blame] | 501 | |
Mike Frysinger | 0c0efa2 | 2014-02-09 23:32:23 -0500 | [diff] [blame] | 502 | class UtilTest(cros_test_lib.TempDirTestCase): |
| 503 | """Various tests for utility funcs.""" |
| 504 | |
| 505 | def testWriteQueueToFile(self): |
| 506 | """Basic test for WriteQueueToFile.""" |
| 507 | listing = os.path.join(self.tempdir, 'list') |
| 508 | exp_list = [ |
| 509 | 'b/c.txt', |
| 510 | 'foo.log', |
| 511 | 'there/might/be/giants', |
| 512 | ] |
| 513 | relpath = '/a' |
| 514 | |
| 515 | q = multiprocessing.Queue() |
| 516 | for f in exp_list: |
| 517 | q.put(os.path.join(relpath, f)) |
Mike Frysinger | 5e6dd71 | 2014-03-07 22:21:17 -0500 | [diff] [blame] | 518 | q.put(None) |
Mike Frysinger | 0c0efa2 | 2014-02-09 23:32:23 -0500 | [diff] [blame] | 519 | upload_symbols.WriteQueueToFile(listing, q, '/a') |
| 520 | |
| 521 | got_list = osutils.ReadFile(listing).splitlines() |
| 522 | self.assertEquals(exp_list, got_list) |
| 523 | |
| 524 | |
Mike Frysinger | ea838d1 | 2014-12-08 11:55:32 -0500 | [diff] [blame] | 525 | def main(_argv): |
Mike Frysinger | d5fcb3a | 2013-05-30 21:10:50 -0400 | [diff] [blame] | 526 | # pylint: disable=W0212 |
| 527 | # Set timeouts small so that if the unit test hangs, it won't hang for long. |
| 528 | parallel._BackgroundTask.STARTUP_TIMEOUT = 5 |
| 529 | parallel._BackgroundTask.EXIT_TIMEOUT = 5 |
| 530 | |
| 531 | # We want to test retry behavior, so make sure we don't sleep. |
| 532 | upload_symbols.INITIAL_RETRY_DELAY = 0 |
| 533 | |
| 534 | # Run the tests. |
Mike Frysinger | ba16737 | 2015-01-21 10:37:03 -0500 | [diff] [blame] | 535 | cros_test_lib.main(level='info', module=__name__) |