blob: c4b2968ee4f62e9c48296e9c37632655554b0af8 [file] [log] [blame]
Mike Frysingere58c0e22017-10-04 15:43:30 -04001# -*- coding: utf-8 -*-
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -04002# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Mike Frysingereb753bf2013-11-22 16:05:35 -05006"""Unittests for upload_symbols.py"""
7
Mike Frysinger7f9be142014-01-15 02:16:42 -05008from __future__ import print_function
9
Mike Frysinger0a2fd922014-09-12 20:23:42 -070010import BaseHTTPServer
Mike Frysinger06da5202014-09-26 17:30:33 -050011import errno
Don Garretta28be6d2016-06-16 18:09:35 -070012import itertools
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040013import os
Mike Frysinger0a2fd922014-09-12 20:23:42 -070014import signal
Mike Frysinger06da5202014-09-26 17:30:33 -050015import socket
Mike Frysinger0a2fd922014-09-12 20:23:42 -070016import SocketServer
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040017import sys
Aviv Keshetd1f04632014-05-09 11:33:46 -070018import time
Mike Frysinger094a2172013-08-14 12:54:35 -040019import urllib2
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040020
Mike Frysinger6db648e2018-07-24 19:57:58 -040021import mock
22
Mike Frysinger079863c2014-10-09 23:16:46 -040023# We specifically set up a local server to connect to, so make sure we
24# delete any proxy settings that might screw that up. We also need to
25# do it here because modules that are imported below will implicitly
26# initialize with this proxy setting rather than dynamically pull it
27# on the fly :(.
28os.environ.pop('http_proxy', None)
29
Aviv Keshetb7519e12016-10-04 00:50:00 -070030from chromite.lib import constants
Mike Frysingerbbd1f112016-09-08 18:25:11 -040031
32# The isolateserver includes a bunch of third_party python packages that clash
33# with chromite's bundled third_party python packages (like oauth2client).
34# Since upload_symbols is not imported in to other parts of chromite, and there
35# are no deps in third_party we care about, purge the chromite copy. This way
36# we can use isolateserver for deduping.
37# TODO: If we ever sort out third_party/ handling and make it per-script opt-in,
38# we can purge this logic.
39third_party = os.path.join(constants.CHROMITE_DIR, 'third_party')
40while True:
41 try:
42 sys.path.remove(third_party)
43 except ValueError:
44 break
Mike Frysingerbbd1f112016-09-08 18:25:11 -040045del third_party
46
Ralph Nathan446aee92015-03-23 14:44:56 -070047from chromite.lib import cros_logging as logging
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040048from chromite.lib import cros_test_lib
49from chromite.lib import osutils
50from chromite.lib import parallel
Mike Frysinger0a2fd922014-09-12 20:23:42 -070051from chromite.lib import remote_access
Mike Frysinger5e30a4b2014-02-12 20:23:04 -050052from chromite.scripts import cros_generate_breakpad_symbols
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040053from chromite.scripts import upload_symbols
54
Mike Frysinger0c0efa22014-02-09 23:32:23 -050055
Don Garretta28be6d2016-06-16 18:09:35 -070056class SymbolsTestBase(cros_test_lib.MockTempDirTestCase):
57 """Base class for most symbols tests."""
58
59 SLIM_CONTENT = """
60some junk
61"""
62
63 FAT_CONTENT = """
64STACK CFI 1234
65some junk
66STACK CFI 1234
67"""
68
69 def setUp(self):
70 # Make certain we don't use the network.
Don Garretta28be6d2016-06-16 18:09:35 -070071 self.urlopen_mock = self.PatchObject(urllib2, 'urlopen')
Mike Nichols90f7c152019-04-09 15:14:08 -060072 self.request_mock = self.PatchObject(upload_symbols, 'ExecRequest',
73 return_value={'uploadUrl':
74 'testurl',
75 'uploadKey':
76 'asdgasgas'})
Don Garretta28be6d2016-06-16 18:09:35 -070077
78 # Make 'uploads' go fast.
79 self.PatchObject(upload_symbols, 'SLEEP_DELAY', 0)
80 self.PatchObject(upload_symbols, 'INITIAL_RETRY_DELAY', 0)
81
82 # So our symbol file content doesn't have to be real.
83 self.PatchObject(cros_generate_breakpad_symbols, 'ReadSymsHeader',
84 return_value=cros_generate_breakpad_symbols.SymbolHeader(
85 os='os', cpu='cpu', id='id', name='name'))
86
87 self.working = os.path.join(self.tempdir, 'expand')
88 osutils.SafeMakedirs(self.working)
89
90 self.data = os.path.join(self.tempdir, 'data')
91 osutils.SafeMakedirs(self.data)
92
93 def createSymbolFile(self, filename, content=FAT_CONTENT, size=0,
94 status=None, dedupe=False):
95 fullname = os.path.join(self.data, filename)
96 osutils.SafeMakedirs(os.path.dirname(fullname))
97
98 # If a file size is given, force that to be the minimum file size. Create
99 # a sparse file so large files are practical.
100 with open(fullname, 'w+b') as f:
101 f.truncate(size)
102 f.seek(0)
103 f.write(content)
104
105 result = upload_symbols.SymbolFile(display_path=filename,
106 file_name=fullname)
107
108 if status:
109 result.status = status
110
111 if dedupe:
112 result.dedupe_item = upload_symbols.DedupeItem(result)
113 result.dedupe_push_state = 'push_state'
114
115 return result
116
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400117
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700118class SymbolServerRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
119 """HTTP handler for symbol POSTs"""
120
121 RESP_CODE = None
122 RESP_MSG = None
123
124 def do_POST(self):
125 """Handle a POST request"""
126 # Drain the data from the client. If we don't, we might write the response
127 # and close the socket before the client finishes, so they die with EPIPE.
128 clen = int(self.headers.get('Content-Length', '0'))
129 self.rfile.read(clen)
130
131 self.send_response(self.RESP_CODE, self.RESP_MSG)
132 self.end_headers()
133
134 def log_message(self, *args, **kwargs):
135 """Stub the logger as it writes to stderr"""
136 pass
137
138
139class SymbolServer(SocketServer.ThreadingTCPServer, BaseHTTPServer.HTTPServer):
140 """Simple HTTP server that forks each request"""
141
142
143class UploadSymbolsServerTest(cros_test_lib.MockTempDirTestCase):
144 """Tests for UploadSymbols() and a local HTTP server"""
145
146 SYM_CONTENTS = """MODULE Linux arm 123-456 blkid
147PUBLIC 1471 0 main"""
148
149 def SpawnServer(self, RequestHandler):
150 """Spawn a new http server"""
Mike Frysinger06da5202014-09-26 17:30:33 -0500151 while True:
152 try:
153 port = remote_access.GetUnusedPort()
154 address = ('', port)
155 self.httpd = SymbolServer(address, RequestHandler)
156 break
157 except socket.error as e:
158 if e.errno == errno.EADDRINUSE:
159 continue
160 raise
Don Garretta28be6d2016-06-16 18:09:35 -0700161 self.server_url = 'http://localhost:%i/post/path' % port
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700162 self.httpd_pid = os.fork()
163 if self.httpd_pid == 0:
164 self.httpd.serve_forever(poll_interval=0.1)
165 sys.exit(0)
166
167 def setUp(self):
168 self.httpd_pid = None
169 self.httpd = None
Don Garretta28be6d2016-06-16 18:09:35 -0700170 self.server_url = None
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700171 self.sym_file = os.path.join(self.tempdir, 'test.sym')
172 osutils.WriteFile(self.sym_file, self.SYM_CONTENTS)
173
Don Garretta28be6d2016-06-16 18:09:35 -0700174 # Stop sleeps and retries for these tests.
175 self.PatchObject(upload_symbols, 'SLEEP_DELAY', 0)
176 self.PatchObject(upload_symbols, 'INITIAL_RETRY_DELAY', 0)
177 self.PatchObject(upload_symbols, 'MAX_RETRIES', 0)
178
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700179 def tearDown(self):
180 # Only kill the server if we forked one.
181 if self.httpd_pid:
182 os.kill(self.httpd_pid, signal.SIGUSR1)
183
184 def testSuccess(self):
185 """The server returns success for all uploads"""
186 class Handler(SymbolServerRequestHandler):
187 """Always return 200"""
188 RESP_CODE = 200
Mike Nichols90f7c152019-04-09 15:14:08 -0600189 self.PatchObject(upload_symbols, 'ExecRequest',
190 return_value={'uploadUrl': 'testurl',
191 'uploadKey': 'testSuccess'})
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700192 self.SpawnServer(Handler)
Don Garretta28be6d2016-06-16 18:09:35 -0700193 ret = upload_symbols.UploadSymbols(
194 sym_paths=[self.sym_file] * 10,
195 upload_url=self.server_url,
Mike Nichols90f7c152019-04-09 15:14:08 -0600196 api_key='testSuccess')
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700197 self.assertEqual(ret, 0)
198
199 def testError(self):
200 """The server returns errors for all uploads"""
201 class Handler(SymbolServerRequestHandler):
Mike Nichols90f7c152019-04-09 15:14:08 -0600202 """All connections error"""
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700203 RESP_CODE = 500
204 RESP_MSG = 'Internal Server Error'
205
206 self.SpawnServer(Handler)
Don Garretta28be6d2016-06-16 18:09:35 -0700207 ret = upload_symbols.UploadSymbols(
208 sym_paths=[self.sym_file] * 10,
209 upload_url=self.server_url,
Mike Nichols90f7c152019-04-09 15:14:08 -0600210 api_key='testkey')
Don Garretta28be6d2016-06-16 18:09:35 -0700211 self.assertEqual(ret, 10)
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700212
213 def testHungServer(self):
214 """The server chokes, but we recover"""
215 class Handler(SymbolServerRequestHandler):
216 """All connections choke forever"""
Mike Nichols137e82d2019-05-15 18:40:34 -0600217 self.PatchObject(upload_symbols, 'ExecRequest',
218 return_value={'pairs': []})
219
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700220 def do_POST(self):
221 while True:
222 time.sleep(1000)
223
224 self.SpawnServer(Handler)
225 with mock.patch.object(upload_symbols, 'GetUploadTimeout') as m:
Don Garretta28be6d2016-06-16 18:09:35 -0700226 m.return_value = 0.01
Mike Frysinger58312e92014-03-18 04:18:36 -0400227 ret = upload_symbols.UploadSymbols(
Don Garretta28be6d2016-06-16 18:09:35 -0700228 sym_paths=[self.sym_file] * 10,
229 upload_url=self.server_url,
Mike Nichols137e82d2019-05-15 18:40:34 -0600230 timeout=m.return_value,
Mike Nichols90f7c152019-04-09 15:14:08 -0600231 api_key='testkey')
Don Garretta28be6d2016-06-16 18:09:35 -0700232 self.assertEqual(ret, 10)
Aviv Keshetd1f04632014-05-09 11:33:46 -0700233
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400234
Don Garretta28be6d2016-06-16 18:09:35 -0700235class UploadSymbolsHelpersTest(cros_test_lib.TestCase):
236 """Test assorted helper functions and classes."""
Don Garretta28be6d2016-06-16 18:09:35 -0700237 def testIsTarball(self):
238 notTar = [
239 '/foo/bar/test.bin',
240 '/foo/bar/test.tar.bin',
241 '/foo/bar/test.faketar.gz',
242 '/foo/bar/test.nottgz',
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500243 ]
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500244
Don Garretta28be6d2016-06-16 18:09:35 -0700245 isTar = [
246 '/foo/bar/test.tar',
247 '/foo/bar/test.bin.tar',
248 '/foo/bar/test.bin.tar.bz2',
249 '/foo/bar/test.bin.tar.gz',
250 '/foo/bar/test.bin.tar.xz',
251 '/foo/bar/test.tbz2',
252 '/foo/bar/test.tbz',
253 '/foo/bar/test.tgz',
254 '/foo/bar/test.txz',
255 ]
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500256
Don Garretta28be6d2016-06-16 18:09:35 -0700257 for p in notTar:
258 self.assertFalse(upload_symbols.IsTarball(p))
259
260 for p in isTar:
261 self.assertTrue(upload_symbols.IsTarball(p))
262
263 def testBatchGenerator(self):
264 result = upload_symbols.BatchGenerator([], 2)
265 self.assertEqual(list(result), [])
266
Mike Frysinger66848312019-07-03 02:12:39 -0400267 # BatchGenerator accepts iterators, so passing it here is safe.
268 # pylint: disable=range-builtin-not-iterating
Mike Frysinger79cca962019-06-13 15:26:53 -0400269 result = upload_symbols.BatchGenerator(range(6), 2)
Don Garretta28be6d2016-06-16 18:09:35 -0700270 self.assertEqual(list(result), [[0, 1], [2, 3], [4, 5]])
271
Mike Frysinger79cca962019-06-13 15:26:53 -0400272 result = upload_symbols.BatchGenerator(range(7), 2)
Don Garretta28be6d2016-06-16 18:09:35 -0700273 self.assertEqual(list(result), [[0, 1], [2, 3], [4, 5], [6]])
274
275 # Prove that we are streaming the results, not generating them all at once.
276 result = upload_symbols.BatchGenerator(itertools.repeat(0), 2)
Mike Frysinger67d90242019-07-03 19:03:58 -0400277 self.assertEqual(next(result), [0, 0])
Don Garretta28be6d2016-06-16 18:09:35 -0700278
279
280class FindSymbolFilesTest(SymbolsTestBase):
281 """Test FindSymbolFiles."""
282 def setUp(self):
283 self.symfile = self.createSymbolFile('root.sym').file_name
284 self.innerfile = self.createSymbolFile(
285 os.path.join('nested', 'inner.sym')).file_name
286
287 # CreateTarball is having issues outside the chroot from open file tests.
288 #
289 # self.tarball = os.path.join(self.tempdir, 'syms.tar.gz')
290 # cros_build_lib.CreateTarball(
291 # 'syms.tar.gz', self.tempdir, inputs=(self.data))
292
293 def testEmpty(self):
294 symbols = list(upload_symbols.FindSymbolFiles(
295 self.working, []))
296 self.assertEqual(symbols, [])
297
298 def testFile(self):
299 symbols = list(upload_symbols.FindSymbolFiles(
300 self.working, [self.symfile]))
301
302 self.assertEqual(len(symbols), 1)
303 sf = symbols[0]
304
305 self.assertEqual(sf.display_name, 'root.sym')
306 self.assertEqual(sf.display_path, self.symfile)
307 self.assertEqual(sf.file_name, self.symfile)
308 self.assertEqual(sf.status, upload_symbols.SymbolFile.INITIAL)
309 self.assertEqual(sf.FileSize(), len(self.FAT_CONTENT))
310
311 def testDir(self):
312 symbols = list(upload_symbols.FindSymbolFiles(
313 self.working, [self.data]))
314
315 self.assertEqual(len(symbols), 2)
316 root = symbols[0]
317 nested = symbols[1]
318
319 self.assertEqual(root.display_name, 'root.sym')
320 self.assertEqual(root.display_path, 'root.sym')
321 self.assertEqual(root.file_name, self.symfile)
322 self.assertEqual(root.status, upload_symbols.SymbolFile.INITIAL)
323 self.assertEqual(root.FileSize(), len(self.FAT_CONTENT))
324
325 self.assertEqual(nested.display_name, 'inner.sym')
326 self.assertEqual(nested.display_path, 'nested/inner.sym')
327 self.assertEqual(nested.file_name, self.innerfile)
328 self.assertEqual(nested.status, upload_symbols.SymbolFile.INITIAL)
329 self.assertEqual(nested.FileSize(), len(self.FAT_CONTENT))
330
331
332class AdjustSymbolFileSizeTest(SymbolsTestBase):
333 """Test AdjustSymbolFileSize."""
334 def setUp(self):
335 self.slim = self.createSymbolFile('slim.sym', self.SLIM_CONTENT)
336 self.fat = self.createSymbolFile('fat.sym', self.FAT_CONTENT)
337
338 self.warn_mock = self.PatchObject(logging, 'PrintBuildbotStepWarnings')
339
340 def _testNotStripped(self, symbol, size=None, content=None):
341 start_file = symbol.file_name
342 after = upload_symbols.AdjustSymbolFileSize(
343 symbol, self.working, size)
344 self.assertIs(after, symbol)
345 self.assertEqual(after.file_name, start_file)
346 if content is not None:
347 self.assertEqual(osutils.ReadFile(after.file_name), content)
348
349 def _testStripped(self, symbol, size=None, content=None):
350 after = upload_symbols.AdjustSymbolFileSize(
351 symbol, self.working, size)
352 self.assertIs(after, symbol)
353 self.assertTrue(after.file_name.startswith(self.working))
354 if content is not None:
355 self.assertEqual(osutils.ReadFile(after.file_name), content)
356
357 def testSmall(self):
358 """Ensure that files smaller than the limit are not modified."""
359 self._testNotStripped(self.slim, 1024, self.SLIM_CONTENT)
360 self._testNotStripped(self.fat, 1024, self.FAT_CONTENT)
361
362 def testLarge(self):
363 """Ensure that files larger than the limit are modified."""
364 self._testStripped(self.slim, 1, self.SLIM_CONTENT)
365 self._testStripped(self.fat, 1, self.SLIM_CONTENT)
366
367 def testMixed(self):
368 """Test mix of large and small."""
369 strip_size = len(self.SLIM_CONTENT) + 1
370
371 self._testNotStripped(self.slim, strip_size, self.SLIM_CONTENT)
372 self._testStripped(self.fat, strip_size, self.SLIM_CONTENT)
373
374 def testSizeWarnings(self):
375 large = self.createSymbolFile(
376 'large.sym', content=self.SLIM_CONTENT,
377 size=upload_symbols.CRASH_SERVER_FILE_LIMIT*2)
378
379 # Would like to Strip as part of this test, but that really copies all
380 # of the sparse file content, which is too expensive for a unittest.
381 self._testNotStripped(large, None, None)
382
383 self.assertEqual(self.warn_mock.call_count, 1)
384
385
Mike Nichols137e82d2019-05-15 18:40:34 -0600386class DeduplicateTest(SymbolsTestBase):
387 """Test server Deduplication."""
388 def setUp(self):
389 self.PatchObject(upload_symbols, 'ExecRequest',
390 return_value={'pairs': [
391 {'status': 'FOUND',
392 'symbolId':
393 {'debugFile': 'sym1_sym',
394 'debugId': 'BEAA9BE'}},
395 {'status': 'FOUND',
396 'symbolId':
397 {'debugFile': 'sym2_sym',
398 'debugId': 'B6B1A36'}},
399 {'status': 'MISSING',
400 'symbolId':
401 {'debugFile': 'sym3_sym',
402 'debugId': 'D4FC0FC'}}]})
403
404 def testFindDuplicates(self):
405 # The first two symbols will be duplicate, the third new.
406 sym1 = self.createSymbolFile('sym1.sym')
407 sym1.header = cros_generate_breakpad_symbols.SymbolHeader('cpu', 'BEAA9BE',
408 'sym1_sym', 'os')
409 sym2 = self.createSymbolFile('sym2.sym')
410 sym2.header = cros_generate_breakpad_symbols.SymbolHeader('cpu', 'B6B1A36',
411 'sym2_sym', 'os')
412 sym3 = self.createSymbolFile('sym3.sym')
413 sym3.header = cros_generate_breakpad_symbols.SymbolHeader('cpu', 'D4FC0FC',
414 'sym3_sym', 'os')
415
416 result = upload_symbols.FindDuplicates((sym1, sym2, sym3), 'fake_url',
417 api_key='testkey')
418 self.assertEqual(list(result), [sym1, sym2, sym3])
419
420 self.assertEqual(sym1.status, upload_symbols.SymbolFile.DUPLICATE)
421 self.assertEqual(sym2.status, upload_symbols.SymbolFile.DUPLICATE)
422 self.assertEqual(sym3.status, upload_symbols.SymbolFile.INITIAL)
423
424
Don Garrettdeb2e032016-07-06 16:44:14 -0700425class PerformSymbolFilesUploadTest(SymbolsTestBase):
Don Garretta28be6d2016-06-16 18:09:35 -0700426 """Test PerformSymbolFile, and it's helper methods."""
427 def setUp(self):
Don Garrettdeb2e032016-07-06 16:44:14 -0700428 self.sym_initial = self.createSymbolFile(
429 'initial.sym')
430 self.sym_error = self.createSymbolFile(
431 'error.sym', status=upload_symbols.SymbolFile.ERROR)
432 self.sym_duplicate = self.createSymbolFile(
433 'duplicate.sym', status=upload_symbols.SymbolFile.DUPLICATE)
434 self.sym_uploaded = self.createSymbolFile(
435 'uploaded.sym', status=upload_symbols.SymbolFile.UPLOADED)
Don Garretta28be6d2016-06-16 18:09:35 -0700436
437 def testGetUploadTimeout(self):
438 """Test GetUploadTimeout helper function."""
439 # Timeout for small file.
Don Garrettdeb2e032016-07-06 16:44:14 -0700440 self.assertEqual(upload_symbols.GetUploadTimeout(self.sym_initial),
Don Garretta28be6d2016-06-16 18:09:35 -0700441 upload_symbols.UPLOAD_MIN_TIMEOUT)
442
443 # Timeout for 300M file.
444 large = self.createSymbolFile('large.sym', size=(300 * 1024 * 1024))
Ryo Hashimotof864c0e2017-02-14 12:46:08 +0900445 self.assertEqual(upload_symbols.GetUploadTimeout(large), 771)
Don Garretta28be6d2016-06-16 18:09:35 -0700446
447 def testUploadSymbolFile(self):
Mike Nichols90f7c152019-04-09 15:14:08 -0600448 upload_symbols.UploadSymbolFile('fake_url', self.sym_initial,
449 api_key='testkey')
Don Garretta28be6d2016-06-16 18:09:35 -0700450 # TODO: Examine mock in more detail to make sure request is correct.
Mike Nichols137e82d2019-05-15 18:40:34 -0600451 self.assertEqual(self.request_mock.call_count, 3)
Don Garretta28be6d2016-06-16 18:09:35 -0700452
Don Garrettdeb2e032016-07-06 16:44:14 -0700453 def testPerformSymbolsFileUpload(self):
Don Garretta28be6d2016-06-16 18:09:35 -0700454 """We upload on first try."""
Don Garrettdeb2e032016-07-06 16:44:14 -0700455 symbols = [self.sym_initial]
Don Garretta28be6d2016-06-16 18:09:35 -0700456
Don Garrettdeb2e032016-07-06 16:44:14 -0700457 result = upload_symbols.PerformSymbolsFileUpload(
Mike Nichols90f7c152019-04-09 15:14:08 -0600458 symbols, 'fake_url', api_key='testkey')
Don Garrettdeb2e032016-07-06 16:44:14 -0700459
460 self.assertEqual(list(result), symbols)
461 self.assertEqual(self.sym_initial.status,
462 upload_symbols.SymbolFile.UPLOADED)
Mike Nichols137e82d2019-05-15 18:40:34 -0600463 self.assertEqual(self.request_mock.call_count, 3)
Don Garretta28be6d2016-06-16 18:09:35 -0700464
Don Garrettdeb2e032016-07-06 16:44:14 -0700465 def testPerformSymbolsFileUploadFailure(self):
Don Garretta28be6d2016-06-16 18:09:35 -0700466 """All network requests fail."""
Mike Nichols90f7c152019-04-09 15:14:08 -0600467 self.request_mock.side_effect = urllib2.URLError('network failure')
Don Garrettdeb2e032016-07-06 16:44:14 -0700468 symbols = [self.sym_initial]
Don Garretta28be6d2016-06-16 18:09:35 -0700469
Don Garrettdeb2e032016-07-06 16:44:14 -0700470 result = upload_symbols.PerformSymbolsFileUpload(
Mike Nichols90f7c152019-04-09 15:14:08 -0600471 symbols, 'fake_url', api_key='testkey')
Don Garretta28be6d2016-06-16 18:09:35 -0700472
Don Garrettdeb2e032016-07-06 16:44:14 -0700473 self.assertEqual(list(result), symbols)
474 self.assertEqual(self.sym_initial.status, upload_symbols.SymbolFile.ERROR)
Mike Nichols90f7c152019-04-09 15:14:08 -0600475 self.assertEqual(self.request_mock.call_count, 7)
Don Garretta28be6d2016-06-16 18:09:35 -0700476
Don Garrettdeb2e032016-07-06 16:44:14 -0700477 def testPerformSymbolsFileUploadTransisentFailure(self):
Don Garretta28be6d2016-06-16 18:09:35 -0700478 """We fail once, then succeed."""
479 self.urlopen_mock.side_effect = (urllib2.URLError('network failure'), None)
Don Garrettdeb2e032016-07-06 16:44:14 -0700480 symbols = [self.sym_initial]
Don Garretta28be6d2016-06-16 18:09:35 -0700481
Don Garrettdeb2e032016-07-06 16:44:14 -0700482 result = upload_symbols.PerformSymbolsFileUpload(
Mike Nichols90f7c152019-04-09 15:14:08 -0600483 symbols, 'fake_url', api_key='testkey')
Don Garretta28be6d2016-06-16 18:09:35 -0700484
Don Garrettdeb2e032016-07-06 16:44:14 -0700485 self.assertEqual(list(result), symbols)
486 self.assertEqual(self.sym_initial.status,
487 upload_symbols.SymbolFile.UPLOADED)
Mike Nichols137e82d2019-05-15 18:40:34 -0600488 self.assertEqual(self.request_mock.call_count, 3)
Don Garrettdeb2e032016-07-06 16:44:14 -0700489
490 def testPerformSymbolsFileUploadMixed(self):
491 """Upload symbols in mixed starting states.
492
493 Demonstrate that INITIAL and ERROR are uploaded, but DUPLICATE/UPLOADED are
494 ignored.
495 """
496 symbols = [self.sym_initial, self.sym_error,
497 self.sym_duplicate, self.sym_uploaded]
498
499 result = upload_symbols.PerformSymbolsFileUpload(
Mike Nichols90f7c152019-04-09 15:14:08 -0600500 symbols, 'fake_url', api_key='testkey')
Don Garrettdeb2e032016-07-06 16:44:14 -0700501
502 #
503 self.assertEqual(list(result), symbols)
504 self.assertEqual(self.sym_initial.status,
505 upload_symbols.SymbolFile.UPLOADED)
506 self.assertEqual(self.sym_error.status,
507 upload_symbols.SymbolFile.UPLOADED)
508 self.assertEqual(self.sym_duplicate.status,
509 upload_symbols.SymbolFile.DUPLICATE)
510 self.assertEqual(self.sym_uploaded.status,
511 upload_symbols.SymbolFile.UPLOADED)
Mike Nichols137e82d2019-05-15 18:40:34 -0600512 self.assertEqual(self.request_mock.call_count, 6)
Don Garrettdeb2e032016-07-06 16:44:14 -0700513
514
515 def testPerformSymbolsFileUploadErrorOut(self):
516 """Demonstate we exit only after X errors."""
517
518 symbol_count = upload_symbols.MAX_TOTAL_ERRORS_FOR_RETRY + 10
519 symbols = []
520 fail_file = None
521
522 # potentially twice as many errors as we should attempt.
Mike Frysinger79cca962019-06-13 15:26:53 -0400523 for _ in range(symbol_count):
Don Garrettdeb2e032016-07-06 16:44:14 -0700524 # Each loop will get unique SymbolFile instances that use the same files.
525 fail = self.createSymbolFile('fail.sym')
526 fail_file = fail.file_name
527 symbols.append(self.createSymbolFile('pass.sym'))
528 symbols.append(fail)
529
530 # Mock out UploadSymbolFile and fail for fail.sym files.
Mike Nichols90f7c152019-04-09 15:14:08 -0600531 def failSome(_url, symbol, _api_key):
Don Garrettdeb2e032016-07-06 16:44:14 -0700532 if symbol.file_name == fail_file:
533 raise urllib2.URLError('network failure')
534
Luigi Semenzato5104f3c2016-10-12 12:37:42 -0700535 upload_mock = self.PatchObject(upload_symbols, 'UploadSymbolFile',
536 side_effect=failSome)
537 upload_mock.__name__ = 'UploadSymbolFileMock2'
Don Garrettdeb2e032016-07-06 16:44:14 -0700538
539 result = upload_symbols.PerformSymbolsFileUpload(
Mike Nichols90f7c152019-04-09 15:14:08 -0600540 symbols, 'fake_url', api_key='testkey')
Don Garrettdeb2e032016-07-06 16:44:14 -0700541
542 self.assertEqual(list(result), symbols)
543
544 passed = sum(s.status == upload_symbols.SymbolFile.UPLOADED
545 for s in symbols)
546 failed = sum(s.status == upload_symbols.SymbolFile.ERROR
547 for s in symbols)
548 skipped = sum(s.status == upload_symbols.SymbolFile.INITIAL
549 for s in symbols)
550
551 # Shows we all pass.sym files worked until limit hit.
552 self.assertEqual(passed, upload_symbols.MAX_TOTAL_ERRORS_FOR_RETRY)
553
554 # Shows we all fail.sym files failed until limit hit.
555 self.assertEqual(failed, upload_symbols.MAX_TOTAL_ERRORS_FOR_RETRY)
556
557 # Shows both pass/fail were skipped after limit hit.
558 self.assertEqual(skipped, 10 * 2)
Don Garretta28be6d2016-06-16 18:09:35 -0700559
560
561class UploadSymbolsTest(SymbolsTestBase):
562 """Test UploadSymbols, along with most helper methods."""
563 def setUp(self):
564 # Results gathering.
565 self.failure_file = os.path.join(self.tempdir, 'failures.txt')
566
567 def testUploadSymbolsEmpty(self):
568 """Upload dir is empty."""
Mike Nichols137e82d2019-05-15 18:40:34 -0600569 result = upload_symbols.UploadSymbols([self.data], 'fake_url')
Don Garretta28be6d2016-06-16 18:09:35 -0700570
571 self.assertEquals(result, 0)
572 self.assertEqual(self.urlopen_mock.call_count, 0)
573
574 def testUploadSymbols(self):
575 """Upload a few files."""
576 self.createSymbolFile('slim.sym', self.SLIM_CONTENT)
577 self.createSymbolFile(os.path.join('nested', 'inner.sym'))
578 self.createSymbolFile('fat.sym', self.FAT_CONTENT)
579
580 result = upload_symbols.UploadSymbols(
Mike Nichols90f7c152019-04-09 15:14:08 -0600581 [self.data], 'fake_url',
582 failed_list=self.failure_file, strip_cfi=len(self.SLIM_CONTENT)+1,
583 api_key='testkey')
Don Garretta28be6d2016-06-16 18:09:35 -0700584
585 self.assertEquals(result, 0)
Mike Nichols137e82d2019-05-15 18:40:34 -0600586 self.assertEqual(self.request_mock.call_count, 10)
Don Garretta28be6d2016-06-16 18:09:35 -0700587 self.assertEquals(osutils.ReadFile(self.failure_file), '')
588
589 def testUploadSymbolsLimited(self):
590 """Upload a few files."""
591 self.createSymbolFile('slim.sym', self.SLIM_CONTENT)
592 self.createSymbolFile(os.path.join('nested', 'inner.sym'))
593 self.createSymbolFile('fat.sym', self.FAT_CONTENT)
594
595 result = upload_symbols.UploadSymbols(
Mike Nichols90f7c152019-04-09 15:14:08 -0600596 [self.data], 'fake_url', upload_limit=2,
597 api_key='testkey')
Don Garretta28be6d2016-06-16 18:09:35 -0700598
599 self.assertEquals(result, 0)
Mike Nichols137e82d2019-05-15 18:40:34 -0600600 self.assertEqual(self.request_mock.call_count, 7)
Mike Frysingerf2fa7d62017-12-14 18:33:59 -0500601 self.assertNotExists(self.failure_file)
Don Garretta28be6d2016-06-16 18:09:35 -0700602
603 def testUploadSymbolsFailures(self):
604 """Upload a few files."""
605 self.createSymbolFile('pass.sym')
606 fail = self.createSymbolFile('fail.sym')
607
Mike Nichols90f7c152019-04-09 15:14:08 -0600608 def failSome(_url, symbol, _api_key):
Don Garretta28be6d2016-06-16 18:09:35 -0700609 if symbol.file_name == fail.file_name:
610 raise urllib2.URLError('network failure')
611
612 # Mock out UploadSymbolFile so it's easy to see which file to fail for.
613 upload_mock = self.PatchObject(upload_symbols, 'UploadSymbolFile',
614 side_effect=failSome)
Luigi Semenzato5104f3c2016-10-12 12:37:42 -0700615 # Mock __name__ for logging.
616 upload_mock.__name__ = 'UploadSymbolFileMock'
Don Garretta28be6d2016-06-16 18:09:35 -0700617
618 result = upload_symbols.UploadSymbols(
Mike Nichols90f7c152019-04-09 15:14:08 -0600619 [self.data], 'fake_url',
620 failed_list=self.failure_file, api_key='testkey')
Don Garretta28be6d2016-06-16 18:09:35 -0700621
622 self.assertEquals(result, 1)
623 self.assertEqual(upload_mock.call_count, 8)
624 self.assertEquals(osutils.ReadFile(self.failure_file), 'fail.sym\n')
625
Don Garretta28be6d2016-06-16 18:09:35 -0700626# TODO: We removed --network integration tests.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500627
628
Mike Frysingerea838d12014-12-08 11:55:32 -0500629def main(_argv):
Mike Frysinger27e21b72018-07-12 14:20:21 -0400630 # pylint: disable=protected-access
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400631 # Set timeouts small so that if the unit test hangs, it won't hang for long.
632 parallel._BackgroundTask.STARTUP_TIMEOUT = 5
633 parallel._BackgroundTask.EXIT_TIMEOUT = 5
634
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400635 # Run the tests.
Mike Frysingerba167372015-01-21 10:37:03 -0500636 cros_test_lib.main(level='info', module=__name__)