2 # 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.
6 """Unittests for upload_symbols.py"""
8 from __future__ import print_function
12 import multiprocessing
17 sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)),
19 from chromite.lib import cros_build_lib
20 from chromite.lib import cros_test_lib
21 from chromite.lib import osutils
22 from chromite.lib import parallel
23 from chromite.lib import parallel_unittest
24 from chromite.scripts import cros_generate_breakpad_symbols
25 from chromite.scripts import upload_symbols
27 # TODO(build): Finish test wrapper (http://crosbug.com/37517).
28 # Until then, this has to be after the chromite imports.
33 class UploadSymbolsTest(cros_test_lib.MockTempDirTestCase):
34 """Tests for UploadSymbols()"""
37 for d in ('foo', 'bar', 'some/dir/here'):
38 d = os.path.join(self.tempdir, d)
39 osutils.SafeMakedirs(d)
40 for f in ('ignored', 'real.sym', 'no.sym.here'):
41 f = os.path.join(d, f)
46 'some/dir/here/real.sym',
49 self.upload_mock = self.PatchObject(upload_symbols, 'UploadSymbol')
50 self.PatchObject(cros_generate_breakpad_symbols, 'ReadSymsHeader',
51 return_value=cros_generate_breakpad_symbols.SymbolHeader(
52 os='os', cpu='cpu', id='id', name='name'))
54 def _testUploadURL(self, official, expected_url):
55 """Helper for checking the url used"""
56 self.upload_mock.return_value = 0
57 with parallel_unittest.ParallelMock():
58 ret = upload_symbols.UploadSymbols('', official=official, retry=False,
59 breakpad_dir=self.tempdir, sleep=0)
60 self.assertEqual(ret, 0)
61 self.assertEqual(self.upload_mock.call_count, 3)
62 for call_args in self.upload_mock.call_args_list:
63 url, sym_item = call_args[0]
64 self.assertEqual(url, expected_url)
65 self.assertTrue(sym_item.sym_file.endswith('.sym'))
67 def testOfficialUploadURL(self):
68 """Verify we upload to the real crash server for official builds"""
69 self._testUploadURL(True, upload_symbols.OFFICIAL_UPLOAD_URL)
71 def testUnofficialUploadURL(self):
72 """Verify we upload to the staging crash server for unofficial builds"""
73 self._testUploadURL(False, upload_symbols.STAGING_UPLOAD_URL)
75 def testUploadSymbolFailureSimple(self):
76 """Verify that when UploadSymbol fails, the error count is passed up"""
77 def UploadSymbol(*_args, **kwargs):
78 kwargs['num_errors'].value = 4
79 self.upload_mock.side_effect = UploadSymbol
80 with parallel_unittest.ParallelMock():
81 ret = upload_symbols.UploadSymbols('', breakpad_dir=self.tempdir, sleep=0,
83 self.assertEquals(ret, 4)
85 def testUploadCount(self):
86 """Verify we can limit the number of uploaded symbols"""
87 self.upload_mock.return_value = 0
89 self.upload_mock.reset_mock()
90 with parallel_unittest.ParallelMock():
91 ret = upload_symbols.UploadSymbols('', breakpad_dir=self.tempdir,
92 sleep=0, upload_limit=c)
93 self.assertEquals(ret, 0)
94 self.assertEqual(self.upload_mock.call_count, c)
96 def testFailedFileList(self):
97 """Verify the failed file list is populated with the right content"""
98 def UploadSymbol(*args, **kwargs):
99 kwargs['failed_queue'].put(args[1].sym_file)
100 kwargs['num_errors'].value = 4
101 self.upload_mock.side_effect = UploadSymbol
102 with parallel_unittest.ParallelMock():
103 failed_list = os.path.join(self.tempdir, 'list')
104 ret = upload_symbols.UploadSymbols('', breakpad_dir=self.tempdir, sleep=0,
105 retry=False, failed_list=failed_list)
106 self.assertEquals(ret, 4)
108 # Need to sort the output as parallel/fs discovery can be unordered.
109 got_list = sorted(osutils.ReadFile(failed_list).splitlines())
110 self.assertEquals(self.sym_paths, got_list)
112 def _testUpload(self, inputs, sym_paths=None):
113 """Helper for testing uploading of specific paths"""
114 if sym_paths is None:
117 self.upload_mock.return_value = 0
118 with parallel_unittest.ParallelMock():
119 ret = upload_symbols.UploadSymbols(sym_paths=inputs, sleep=0,
121 self.assertEquals(ret, 0)
122 self.assertEquals(self.upload_mock.call_count, len(sym_paths))
124 # Since upload order is arbitrary, we have to do a manual scan for each
125 # path ourselves against the uploaded file list.
126 found_syms = [x[0][1].sym_file for x in self.upload_mock.call_args_list]
127 for found_sym in found_syms:
128 for path in sym_paths:
129 if found_sym.endswith(path):
132 raise AssertionError('Could not locate %s in %r' % (path, found_syms))
134 def testUploadFiles(self):
135 """Test uploading specific symbol files"""
137 os.path.join(self.tempdir, 'bar', 'real.sym'),
138 os.path.join(self.tempdir, 'foo', 'real.sym'),
140 self._testUpload(sym_paths)
142 def testUploadDirectory(self):
143 """Test uploading directory of symbol files"""
144 self._testUpload([self.tempdir], sym_paths=self.sym_paths)
146 def testUploadLocalTarball(self):
147 """Test uploading symbols contains in a local tarball"""
148 tarball = os.path.join(self.tempdir, 'syms.tar.gz')
149 cros_build_lib.CreateTarball(
150 'syms.tar.gz', self.tempdir, compression=cros_build_lib.COMP_GZIP,
151 inputs=('foo', 'bar', 'some'))
152 self._testUpload([tarball], sym_paths=self.sym_paths)
154 def testUploadRemoteTarball(self):
155 """Test uploading symbols contains in a remote tarball"""
156 # TODO: Need to figure out how to mock out lib.cache.TarballCache.
158 def testDedupeNotifyFailure(self):
159 """Test that a dedupe server failure midway doesn't wedge things"""
160 api_mock = mock.MagicMock()
162 def _Contains(items):
163 """Do not dedupe anything"""
165 api_mock.contains.side_effect = _Contains
167 # Use a list so the closure below can modify the value.
169 # Pick a number big enough to trigger a hang normally, but not so
170 # big it adds a lot of overhead.
173 """Die in the middle of the push list"""
175 if item_count[0] > (item_limit / 10):
176 raise ValueError('time to die')
177 api_mock.push.side_effect = _Push
179 self.PatchObject(isolateserver, 'get_storage_api', return_value=api_mock)
181 def _Uploader(*args, **kwargs):
182 """Pass the uploaded symbol to the deduper"""
184 passed_queue = kwargs['passed_queue']
185 passed_queue.put(sym_item)
186 self.upload_mock.side_effect = _Uploader
188 self.upload_mock.return_value = 0
189 with parallel_unittest.ParallelMock():
190 ret = upload_symbols.UploadSymbols(
191 '', sym_paths=[self.tempdir] * item_limit, sleep=0,
192 dedupe_namespace='inva!id name$pace')
193 self.assertEqual(ret, 0)
194 # This test normally passes by not hanging.
197 class SymbolDeduplicatorNotifyTest(cros_test_lib.MockTestCase):
198 """Tests for SymbolDeduplicatorNotify()"""
201 self.storage_mock = self.PatchObject(isolateserver, 'get_storage_api')
204 """Basic run through the system."""
206 q.get.side_effect = (upload_symbols.FakeItem(), None,)
207 upload_symbols.SymbolDeduplicatorNotify('name', q)
209 def testStorageException(self):
210 """We want to just warn & move on when dedupe server fails"""
211 log_mock = self.PatchObject(cros_build_lib, 'Warning')
213 q.get.side_effect = (upload_symbols.FakeItem(), None,)
214 self.storage_mock.side_effect = Exception
215 upload_symbols.SymbolDeduplicatorNotify('name', q)
216 self.assertEqual(log_mock.call_count, 1)
219 class SymbolDeduplicatorTest(cros_test_lib.MockTestCase):
220 """Tests for SymbolDeduplicator()"""
223 self.storage_mock = mock.MagicMock()
224 self.header_mock = self.PatchObject(
225 cros_generate_breakpad_symbols, 'ReadSymsHeader',
226 return_value=cros_generate_breakpad_symbols.SymbolHeader(
227 os='os', cpu='cpu', id='id', name='name'))
229 def testNoStorageOrPaths(self):
230 """We don't want to talk to the server if there's no storage or files"""
231 upload_symbols.SymbolDeduplicator(None, [])
232 upload_symbols.SymbolDeduplicator(self.storage_mock, [])
233 self.assertEqual(self.storage_mock.call_count, 0)
234 self.assertEqual(self.header_mock.call_count, 0)
236 def testStorageException(self):
237 """We want to just warn & move on when dedupe server fails"""
238 log_mock = self.PatchObject(cros_build_lib, 'Warning')
239 self.storage_mock.contains.side_effect = Exception('storage error')
240 sym_paths = ['/a', '/bbbbbb', '/cc.c']
241 ret = upload_symbols.SymbolDeduplicator(self.storage_mock, sym_paths)
242 self.assertEqual(log_mock.call_count, 1)
243 self.assertEqual(self.storage_mock.contains.call_count, 1)
244 self.assertEqual(self.header_mock.call_count, len(sym_paths))
245 self.assertEqual(len(ret), len(sym_paths))
248 class UploadSymbolTest(cros_test_lib.MockTempDirTestCase):
249 """Tests for UploadSymbol()"""
252 self.sym_file = os.path.join(self.tempdir, 'foo.sym')
253 self.sym_item = upload_symbols.FakeItem(sym_file=self.sym_file)
254 self.url = 'http://eatit'
255 self.upload_mock = self.PatchObject(upload_symbols, 'SymUpload')
257 def testUploadSymbolNormal(self):
258 """Verify we try to upload on a normal file"""
259 osutils.Touch(self.sym_file)
260 ret = upload_symbols.UploadSymbol(self.url, self.sym_item)
261 self.assertEqual(ret, 0)
262 self.upload_mock.assert_called_with(self.url, self.sym_item)
263 self.assertEqual(self.upload_mock.call_count, 1)
265 def testUploadSymbolErrorCountExceeded(self):
266 """Verify that when the error count gets too high, we stop uploading"""
267 errors = ctypes.c_int(10000)
268 # Pass in garbage values so that we crash if num_errors isn't handled.
269 ret = upload_symbols.UploadSymbol(None, self.sym_item, sleep=None,
271 self.assertEqual(ret, 0)
273 def testUploadRetryErrors(self, side_effect=None):
274 """Verify that we retry errors (and eventually give up)"""
276 side_effect = urllib2.HTTPError('http://', 400, 'fail', {}, None)
277 self.upload_mock.side_effect = side_effect
278 errors = ctypes.c_int()
279 item = upload_symbols.FakeItem(sym_file='/dev/null')
280 ret = upload_symbols.UploadSymbol(self.url, item, num_errors=errors)
281 self.assertEqual(ret, 1)
282 self.upload_mock.assert_called_with(self.url, item)
283 self.assertTrue(self.upload_mock.call_count >= upload_symbols.MAX_RETRIES)
285 def testConnectRetryErrors(self):
286 """Verify that we retry errors (and eventually give up) w/connect errors"""
287 side_effect = urllib2.URLError('foo')
288 self.testUploadRetryErrors(side_effect=side_effect)
290 def testTruncateTooBigFiles(self):
291 """Verify we shrink big files"""
292 def SymUpload(_url, sym_item):
293 content = osutils.ReadFile(sym_item.sym_file)
294 self.assertEqual(content, 'some junk\n')
295 self.upload_mock.upload_mock.side_effect = SymUpload
296 content = '\n'.join((
301 osutils.WriteFile(self.sym_file, content)
302 ret = upload_symbols.UploadSymbol(self.url, self.sym_item, file_limit=1)
303 self.assertEqual(ret, 0)
304 # Make sure the item passed to the upload has a temp file and not the
305 # original -- only the temp one has been stripped down.
306 temp_item = self.upload_mock.call_args[0][1]
307 self.assertNotEqual(temp_item.sym_file, self.sym_item.sym_file)
308 self.assertEqual(self.upload_mock.call_count, 1)
310 def testTruncateReallyLargeFiles(self):
311 """Verify we try to shrink really big files"""
312 warn_mock = self.PatchObject(cros_build_lib, 'PrintBuildbotStepWarnings')
313 with open(self.sym_file, 'w+b') as f:
314 f.truncate(upload_symbols.CRASH_SERVER_FILE_LIMIT + 100)
316 f.write('STACK CFI 1234\n\n')
317 ret = upload_symbols.UploadSymbol(self.url, self.sym_item)
318 self.assertEqual(ret, 0)
319 # Make sure the item passed to the upload has a temp file and not the
320 # original -- only the temp one has been truncated.
321 temp_item = self.upload_mock.call_args[0][1]
322 self.assertNotEqual(temp_item.sym_file, self.sym_item.sym_file)
323 self.assertEqual(self.upload_mock.call_count, 1)
324 self.assertEqual(warn_mock.call_count, 1)
327 class SymUploadTest(cros_test_lib.MockTempDirTestCase):
328 """Tests for SymUpload()"""
330 SYM_URL = 'http://localhost/post/it/here'
331 SYM_CONTENTS = """MODULE Linux arm 123-456 blkid
332 PUBLIC 1471 0 main"""
335 self.sym_file = os.path.join(self.tempdir, 'test.sym')
336 osutils.WriteFile(self.sym_file, self.SYM_CONTENTS)
337 self.sym_item = upload_symbols.SymbolItem(self.sym_file)
339 def testPostUpload(self):
340 """Verify HTTP POST has all the fields we need"""
341 m = self.PatchObject(urllib2, 'urlopen', autospec=True)
342 upload_symbols.SymUpload(self.SYM_URL, self.sym_item)
343 self.assertEquals(m.call_count, 1)
344 req = m.call_args[0][0]
345 self.assertEquals(req.get_full_url(), self.SYM_URL)
346 data = ''.join([x for x in req.get_data()])
349 'code_file': 'blkid',
350 'debug_file': 'blkid',
351 'debug_identifier': '123456',
355 for key, val in fields.iteritems():
356 line = 'Content-Disposition: form-data; name="%s"\r\n' % key
357 self.assertTrue(line in data)
358 line = '%s\r\n' % val
359 self.assertTrue(line in data)
360 line = ('Content-Disposition: form-data; name="symbol_file"; '
361 'filename="test.sym"\r\n')
362 self.assertTrue(line in data)
363 self.assertTrue(self.SYM_CONTENTS in data)
366 class UtilTest(cros_test_lib.TempDirTestCase):
367 """Various tests for utility funcs."""
369 def testWriteQueueToFile(self):
370 """Basic test for WriteQueueToFile."""
371 listing = os.path.join(self.tempdir, 'list')
375 'there/might/be/giants',
379 q = multiprocessing.Queue()
381 q.put(os.path.join(relpath, f))
383 upload_symbols.WriteQueueToFile(listing, q, '/a')
385 got_list = osutils.ReadFile(listing).splitlines()
386 self.assertEquals(exp_list, got_list)
389 if __name__ == '__main__':
390 # pylint: disable=W0212
391 # Set timeouts small so that if the unit test hangs, it won't hang for long.
392 parallel._BackgroundTask.STARTUP_TIMEOUT = 5
393 parallel._BackgroundTask.EXIT_TIMEOUT = 5
395 # We want to test retry behavior, so make sure we don't sleep.
396 upload_symbols.INITIAL_RETRY_DELAY = 0
399 cros_test_lib.main(level=logging.INFO)