blob: 0e1ac1ffa835a296479ad65cd8b85068b78922c8 [file] [log] [blame]
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -04001#!/usr/bin/python
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.
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 Frysingerd5fcb3a2013-05-30 21:10:50 -040010import ctypes
11import logging
Mike Frysinger0c0efa22014-02-09 23:32:23 -050012import multiprocessing
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040013import os
14import sys
Mike Frysinger094a2172013-08-14 12:54:35 -040015import urllib2
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040016
17sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)),
18 '..', '..'))
Mike Frysinger02e92402013-11-22 16:22:02 -050019from chromite.lib import cros_build_lib
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040020from chromite.lib import cros_test_lib
21from chromite.lib import osutils
22from chromite.lib import parallel
23from chromite.lib import parallel_unittest
Mike Frysinger5e30a4b2014-02-12 20:23:04 -050024from chromite.scripts import cros_generate_breakpad_symbols
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040025from chromite.scripts import upload_symbols
26
Mike Frysinger0c0efa22014-02-09 23:32:23 -050027# TODO(build): Finish test wrapper (http://crosbug.com/37517).
28# Until then, this has to be after the chromite imports.
29import isolateserver
30import mock
31
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040032
33class UploadSymbolsTest(cros_test_lib.MockTempDirTestCase):
Mike Frysingerc4ab5782013-10-02 18:14:22 -040034 """Tests for UploadSymbols()"""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040035
36 def setUp(self):
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)
42 osutils.Touch(f)
Mike Frysingerd41938e2014-02-10 06:37:55 -050043 self.sym_paths = [
44 'bar/real.sym',
45 'foo/real.sym',
46 'some/dir/here/real.sym',
47 ]
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040048
Mike Frysinger5e30a4b2014-02-12 20:23:04 -050049 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'))
53
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040054 def _testUploadURL(self, official, expected_url):
55 """Helper for checking the url used"""
Mike Frysinger5e30a4b2014-02-12 20:23:04 -050056 self.upload_mock.return_value = 0
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040057 with parallel_unittest.ParallelMock():
Mike Frysinger02e92402013-11-22 16:22:02 -050058 ret = upload_symbols.UploadSymbols('', official=official, retry=False,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040059 breakpad_dir=self.tempdir, sleep=0)
60 self.assertEqual(ret, 0)
Mike Frysinger5e30a4b2014-02-12 20:23:04 -050061 self.assertEqual(self.upload_mock.call_count, 3)
62 for call_args in self.upload_mock.call_args_list:
Mike Frysinger0c0efa22014-02-09 23:32:23 -050063 url, sym_item = call_args[0]
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040064 self.assertEqual(url, expected_url)
Mike Frysinger0c0efa22014-02-09 23:32:23 -050065 self.assertTrue(sym_item.sym_file.endswith('.sym'))
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040066
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)
70
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)
74
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
Mike Frysinger5e30a4b2014-02-12 20:23:04 -050079 self.upload_mock.side_effect = UploadSymbol
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040080 with parallel_unittest.ParallelMock():
Mike Frysinger02e92402013-11-22 16:22:02 -050081 ret = upload_symbols.UploadSymbols('', breakpad_dir=self.tempdir, sleep=0,
82 retry=False)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040083 self.assertEquals(ret, 4)
84
85 def testUploadCount(self):
86 """Verify we can limit the number of uploaded symbols"""
Mike Frysinger5e30a4b2014-02-12 20:23:04 -050087 self.upload_mock.return_value = 0
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040088 for c in xrange(3):
Mike Frysinger5e30a4b2014-02-12 20:23:04 -050089 self.upload_mock.reset_mock()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040090 with parallel_unittest.ParallelMock():
91 ret = upload_symbols.UploadSymbols('', breakpad_dir=self.tempdir,
Mike Frysinger8ec8c502014-02-10 00:19:13 -050092 sleep=0, upload_limit=c)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040093 self.assertEquals(ret, 0)
Mike Frysinger5e30a4b2014-02-12 20:23:04 -050094 self.assertEqual(self.upload_mock.call_count, c)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040095
Mike Frysinger7f9be142014-01-15 02:16:42 -050096 def testFailedFileList(self):
97 """Verify the failed file list is populated with the right content"""
98 def UploadSymbol(*args, **kwargs):
Mike Frysinger0c0efa22014-02-09 23:32:23 -050099 kwargs['failed_queue'].put(args[1].sym_file)
Mike Frysinger7f9be142014-01-15 02:16:42 -0500100 kwargs['num_errors'].value = 4
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500101 self.upload_mock.side_effect = UploadSymbol
Mike Frysinger7f9be142014-01-15 02:16:42 -0500102 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)
107
108 # Need to sort the output as parallel/fs discovery can be unordered.
109 got_list = sorted(osutils.ReadFile(failed_list).splitlines())
Mike Frysingerd41938e2014-02-10 06:37:55 -0500110 self.assertEquals(self.sym_paths, got_list)
111
112 def _testUpload(self, inputs, sym_paths=None):
113 """Helper for testing uploading of specific paths"""
114 if sym_paths is None:
115 sym_paths = inputs
116
117 self.upload_mock.return_value = 0
118 with parallel_unittest.ParallelMock():
119 ret = upload_symbols.UploadSymbols(sym_paths=inputs, sleep=0,
120 retry=False)
121 self.assertEquals(ret, 0)
122 self.assertEquals(self.upload_mock.call_count, len(sym_paths))
123
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):
130 break
131 else:
132 raise AssertionError('Could not locate %s in %r' % (path, found_syms))
133
134 def testUploadFiles(self):
135 """Test uploading specific symbol files"""
136 sym_paths = (
137 os.path.join(self.tempdir, 'bar', 'real.sym'),
138 os.path.join(self.tempdir, 'foo', 'real.sym'),
139 )
140 self._testUpload(sym_paths)
141
142 def testUploadDirectory(self):
143 """Test uploading directory of symbol files"""
144 self._testUpload([self.tempdir], sym_paths=self.sym_paths)
145
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)
153
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.
Mike Frysinger7f9be142014-01-15 02:16:42 -0500157
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400158
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500159class SymbolDeduplicatorNotifyTest(cros_test_lib.MockTestCase):
160 """Tests for SymbolDeduplicatorNotify()"""
161
162 def setUp(self):
163 self.storage_mock = self.PatchObject(isolateserver, 'get_storage_api')
164
165 def testSmoke(self):
166 """Basic run through the system."""
167 q = mock.MagicMock()
168 q.get.side_effect = (upload_symbols.FakeItem(), None,)
169 upload_symbols.SymbolDeduplicatorNotify('name', q)
170
171 def testStorageException(self):
172 """We want to just warn & move on when dedupe server fails"""
173 log_mock = self.PatchObject(cros_build_lib, 'Warning')
174 q = mock.MagicMock()
175 q.get.side_effect = (upload_symbols.FakeItem(), None,)
176 self.storage_mock.side_effect = Exception
177 upload_symbols.SymbolDeduplicatorNotify('name', q)
178 self.assertEqual(log_mock.call_count, 1)
179
180
181class SymbolDeduplicatorTest(cros_test_lib.MockTestCase):
182 """Tests for SymbolDeduplicator()"""
183
184 def setUp(self):
185 self.storage_mock = mock.MagicMock()
186 self.header_mock = self.PatchObject(
187 cros_generate_breakpad_symbols, 'ReadSymsHeader',
188 return_value=cros_generate_breakpad_symbols.SymbolHeader(
189 os='os', cpu='cpu', id='id', name='name'))
190
191 def testNoStorageOrPaths(self):
192 """We don't want to talk to the server if there's no storage or files"""
193 upload_symbols.SymbolDeduplicator(None, [])
194 upload_symbols.SymbolDeduplicator(self.storage_mock, [])
195 self.assertEqual(self.storage_mock.call_count, 0)
196 self.assertEqual(self.header_mock.call_count, 0)
197
198 def testStorageException(self):
199 """We want to just warn & move on when dedupe server fails"""
200 log_mock = self.PatchObject(cros_build_lib, 'Warning')
201 self.storage_mock.contains.side_effect = Exception('storage error')
202 sym_paths = ['/a', '/bbbbbb', '/cc.c']
203 ret = upload_symbols.SymbolDeduplicator(self.storage_mock, sym_paths)
204 self.assertEqual(log_mock.call_count, 1)
205 self.assertEqual(self.storage_mock.contains.call_count, 1)
206 self.assertEqual(self.header_mock.call_count, len(sym_paths))
207 self.assertEqual(len(ret), len(sym_paths))
208
209
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400210class UploadSymbolTest(cros_test_lib.MockTempDirTestCase):
Mike Frysingerc4ab5782013-10-02 18:14:22 -0400211 """Tests for UploadSymbol()"""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400212
213 def setUp(self):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400214 self.sym_file = os.path.join(self.tempdir, 'foo.sym')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500215 self.sym_item = upload_symbols.FakeItem(sym_file=self.sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400216 self.url = 'http://eatit'
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500217 self.upload_mock = self.PatchObject(upload_symbols, 'SymUpload')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400218
219 def testUploadSymbolNormal(self):
220 """Verify we try to upload on a normal file"""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400221 osutils.Touch(self.sym_file)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500222 ret = upload_symbols.UploadSymbol(self.url, self.sym_item)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400223 self.assertEqual(ret, 0)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500224 self.upload_mock.assert_called_with(self.url, self.sym_item)
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500225 self.assertEqual(self.upload_mock.call_count, 1)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400226
227 def testUploadSymbolErrorCountExceeded(self):
228 """Verify that when the error count gets too high, we stop uploading"""
229 errors = ctypes.c_int(10000)
230 # Pass in garbage values so that we crash if num_errors isn't handled.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500231 ret = upload_symbols.UploadSymbol(None, self.sym_item, sleep=None,
232 num_errors=errors)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400233 self.assertEqual(ret, 0)
234
Mike Frysingerc4ab5782013-10-02 18:14:22 -0400235 def testUploadRetryErrors(self, side_effect=None):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400236 """Verify that we retry errors (and eventually give up)"""
Mike Frysingerc4ab5782013-10-02 18:14:22 -0400237 if not side_effect:
238 side_effect = urllib2.HTTPError('http://', 400, 'fail', {}, None)
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500239 self.upload_mock.side_effect = side_effect
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400240 errors = ctypes.c_int()
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500241 item = upload_symbols.FakeItem(sym_file='/dev/null')
242 ret = upload_symbols.UploadSymbol(self.url, item, num_errors=errors)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400243 self.assertEqual(ret, 1)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500244 self.upload_mock.assert_called_with(self.url, item)
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500245 self.assertTrue(self.upload_mock.call_count >= upload_symbols.MAX_RETRIES)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400246
Mike Frysingerc4ab5782013-10-02 18:14:22 -0400247 def testConnectRetryErrors(self):
248 """Verify that we retry errors (and eventually give up) w/connect errors"""
249 side_effect = urllib2.URLError('foo')
250 self.testUploadRetryErrors(side_effect=side_effect)
251
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400252 def testTruncateTooBigFiles(self):
253 """Verify we shrink big files"""
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500254 def SymUpload(_url, sym_item):
255 content = osutils.ReadFile(sym_item.sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400256 self.assertEqual(content, 'some junk\n')
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500257 self.upload_mock.upload_mock.side_effect = SymUpload
258 content = '\n'.join((
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400259 'STACK CFI 1234',
260 'some junk',
261 'STACK CFI 1234',
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500262 ))
263 osutils.WriteFile(self.sym_file, content)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500264 ret = upload_symbols.UploadSymbol(self.url, self.sym_item, file_limit=1)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400265 self.assertEqual(ret, 0)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500266 # Make sure the item passed to the upload has a temp file and not the
267 # original -- only the temp one has been stripped down.
268 temp_item = self.upload_mock.call_args[0][1]
269 self.assertNotEqual(temp_item.sym_file, self.sym_item.sym_file)
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500270 self.assertEqual(self.upload_mock.call_count, 1)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400271
272 def testTruncateReallyLargeFiles(self):
273 """Verify we try to shrink really big files"""
Mike Frysinger02e92402013-11-22 16:22:02 -0500274 warn_mock = self.PatchObject(cros_build_lib, 'PrintBuildbotStepWarnings')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400275 with open(self.sym_file, 'w+b') as f:
276 f.truncate(upload_symbols.CRASH_SERVER_FILE_LIMIT + 100)
277 f.seek(0)
278 f.write('STACK CFI 1234\n\n')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500279 ret = upload_symbols.UploadSymbol(self.url, self.sym_item)
Mike Frysinger02e92402013-11-22 16:22:02 -0500280 self.assertEqual(ret, 0)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500281 # Make sure the item passed to the upload has a temp file and not the
282 # original -- only the temp one has been truncated.
283 temp_item = self.upload_mock.call_args[0][1]
284 self.assertNotEqual(temp_item.sym_file, self.sym_item.sym_file)
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500285 self.assertEqual(self.upload_mock.call_count, 1)
Mike Frysinger02e92402013-11-22 16:22:02 -0500286 self.assertEqual(warn_mock.call_count, 1)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400287
288
Mike Frysinger094a2172013-08-14 12:54:35 -0400289class SymUploadTest(cros_test_lib.MockTempDirTestCase):
Mike Frysingerc4ab5782013-10-02 18:14:22 -0400290 """Tests for SymUpload()"""
Mike Frysinger094a2172013-08-14 12:54:35 -0400291
292 SYM_URL = 'http://localhost/post/it/here'
293 SYM_CONTENTS = """MODULE Linux arm 123-456 blkid
294PUBLIC 1471 0 main"""
295
296 def setUp(self):
297 self.sym_file = os.path.join(self.tempdir, 'test.sym')
298 osutils.WriteFile(self.sym_file, self.SYM_CONTENTS)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500299 self.sym_item = upload_symbols.SymbolItem(self.sym_file)
Mike Frysinger094a2172013-08-14 12:54:35 -0400300
301 def testPostUpload(self):
302 """Verify HTTP POST has all the fields we need"""
303 m = self.PatchObject(urllib2, 'urlopen', autospec=True)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500304 upload_symbols.SymUpload(self.SYM_URL, self.sym_item)
Mike Frysinger094a2172013-08-14 12:54:35 -0400305 self.assertEquals(m.call_count, 1)
306 req = m.call_args[0][0]
307 self.assertEquals(req.get_full_url(), self.SYM_URL)
308 data = ''.join([x for x in req.get_data()])
309
310 fields = {
311 'code_file': 'blkid',
312 'debug_file': 'blkid',
313 'debug_identifier': '123456',
314 'os': 'Linux',
315 'cpu': 'arm',
316 }
317 for key, val in fields.iteritems():
318 line = 'Content-Disposition: form-data; name="%s"\r\n' % key
319 self.assertTrue(line in data)
320 line = '%s\r\n' % val
321 self.assertTrue(line in data)
322 line = ('Content-Disposition: form-data; name="symbol_file"; '
323 'filename="test.sym"\r\n')
324 self.assertTrue(line in data)
325 self.assertTrue(self.SYM_CONTENTS in data)
326
327
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500328class UtilTest(cros_test_lib.TempDirTestCase):
329 """Various tests for utility funcs."""
330
331 def testWriteQueueToFile(self):
332 """Basic test for WriteQueueToFile."""
333 listing = os.path.join(self.tempdir, 'list')
334 exp_list = [
335 'b/c.txt',
336 'foo.log',
337 'there/might/be/giants',
338 ]
339 relpath = '/a'
340
341 q = multiprocessing.Queue()
342 for f in exp_list:
343 q.put(os.path.join(relpath, f))
344 upload_symbols.WriteQueueToFile(listing, q, '/a')
345
346 got_list = osutils.ReadFile(listing).splitlines()
347 self.assertEquals(exp_list, got_list)
348
349
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400350if __name__ == '__main__':
351 # pylint: disable=W0212
352 # Set timeouts small so that if the unit test hangs, it won't hang for long.
353 parallel._BackgroundTask.STARTUP_TIMEOUT = 5
354 parallel._BackgroundTask.EXIT_TIMEOUT = 5
355
356 # We want to test retry behavior, so make sure we don't sleep.
357 upload_symbols.INITIAL_RETRY_DELAY = 0
358
359 # Run the tests.
360 cros_test_lib.main(level=logging.INFO)