blob: ddf4163a4433d4c4a800365508acf2f855db670d [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)
43
Mike Frysinger5e30a4b2014-02-12 20:23:04 -050044 self.upload_mock = self.PatchObject(upload_symbols, 'UploadSymbol')
45 self.PatchObject(cros_generate_breakpad_symbols, 'ReadSymsHeader',
46 return_value=cros_generate_breakpad_symbols.SymbolHeader(
47 os='os', cpu='cpu', id='id', name='name'))
48
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040049 def _testUploadURL(self, official, expected_url):
50 """Helper for checking the url used"""
Mike Frysinger5e30a4b2014-02-12 20:23:04 -050051 self.upload_mock.return_value = 0
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040052 with parallel_unittest.ParallelMock():
Mike Frysinger02e92402013-11-22 16:22:02 -050053 ret = upload_symbols.UploadSymbols('', official=official, retry=False,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040054 breakpad_dir=self.tempdir, sleep=0)
55 self.assertEqual(ret, 0)
Mike Frysinger5e30a4b2014-02-12 20:23:04 -050056 self.assertEqual(self.upload_mock.call_count, 3)
57 for call_args in self.upload_mock.call_args_list:
Mike Frysinger0c0efa22014-02-09 23:32:23 -050058 url, sym_item = call_args[0]
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040059 self.assertEqual(url, expected_url)
Mike Frysinger0c0efa22014-02-09 23:32:23 -050060 self.assertTrue(sym_item.sym_file.endswith('.sym'))
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040061
62 def testOfficialUploadURL(self):
63 """Verify we upload to the real crash server for official builds"""
64 self._testUploadURL(True, upload_symbols.OFFICIAL_UPLOAD_URL)
65
66 def testUnofficialUploadURL(self):
67 """Verify we upload to the staging crash server for unofficial builds"""
68 self._testUploadURL(False, upload_symbols.STAGING_UPLOAD_URL)
69
70 def testUploadSymbolFailureSimple(self):
71 """Verify that when UploadSymbol fails, the error count is passed up"""
72 def UploadSymbol(*_args, **kwargs):
73 kwargs['num_errors'].value = 4
Mike Frysinger5e30a4b2014-02-12 20:23:04 -050074 self.upload_mock.side_effect = UploadSymbol
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040075 with parallel_unittest.ParallelMock():
Mike Frysinger02e92402013-11-22 16:22:02 -050076 ret = upload_symbols.UploadSymbols('', breakpad_dir=self.tempdir, sleep=0,
77 retry=False)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040078 self.assertEquals(ret, 4)
79
80 def testUploadCount(self):
81 """Verify we can limit the number of uploaded symbols"""
Mike Frysinger5e30a4b2014-02-12 20:23:04 -050082 self.upload_mock.return_value = 0
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040083 for c in xrange(3):
Mike Frysinger5e30a4b2014-02-12 20:23:04 -050084 self.upload_mock.reset_mock()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040085 with parallel_unittest.ParallelMock():
86 ret = upload_symbols.UploadSymbols('', breakpad_dir=self.tempdir,
Mike Frysinger8ec8c502014-02-10 00:19:13 -050087 sleep=0, upload_limit=c)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040088 self.assertEquals(ret, 0)
Mike Frysinger5e30a4b2014-02-12 20:23:04 -050089 self.assertEqual(self.upload_mock.call_count, c)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040090
Mike Frysinger7f9be142014-01-15 02:16:42 -050091 def testFailedFileList(self):
92 """Verify the failed file list is populated with the right content"""
93 def UploadSymbol(*args, **kwargs):
Mike Frysinger0c0efa22014-02-09 23:32:23 -050094 kwargs['failed_queue'].put(args[1].sym_file)
Mike Frysinger7f9be142014-01-15 02:16:42 -050095 kwargs['num_errors'].value = 4
Mike Frysinger5e30a4b2014-02-12 20:23:04 -050096 self.upload_mock.side_effect = UploadSymbol
Mike Frysinger7f9be142014-01-15 02:16:42 -050097 with parallel_unittest.ParallelMock():
98 failed_list = os.path.join(self.tempdir, 'list')
99 ret = upload_symbols.UploadSymbols('', breakpad_dir=self.tempdir, sleep=0,
100 retry=False, failed_list=failed_list)
101 self.assertEquals(ret, 4)
102
103 # Need to sort the output as parallel/fs discovery can be unordered.
104 got_list = sorted(osutils.ReadFile(failed_list).splitlines())
105 exp_list = [
106 'bar/real.sym',
107 'foo/real.sym',
108 'some/dir/here/real.sym',
109 ]
110 self.assertEquals(exp_list, got_list)
111
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400112
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500113class SymbolDeduplicatorNotifyTest(cros_test_lib.MockTestCase):
114 """Tests for SymbolDeduplicatorNotify()"""
115
116 def setUp(self):
117 self.storage_mock = self.PatchObject(isolateserver, 'get_storage_api')
118
119 def testSmoke(self):
120 """Basic run through the system."""
121 q = mock.MagicMock()
122 q.get.side_effect = (upload_symbols.FakeItem(), None,)
123 upload_symbols.SymbolDeduplicatorNotify('name', q)
124
125 def testStorageException(self):
126 """We want to just warn & move on when dedupe server fails"""
127 log_mock = self.PatchObject(cros_build_lib, 'Warning')
128 q = mock.MagicMock()
129 q.get.side_effect = (upload_symbols.FakeItem(), None,)
130 self.storage_mock.side_effect = Exception
131 upload_symbols.SymbolDeduplicatorNotify('name', q)
132 self.assertEqual(log_mock.call_count, 1)
133
134
135class SymbolDeduplicatorTest(cros_test_lib.MockTestCase):
136 """Tests for SymbolDeduplicator()"""
137
138 def setUp(self):
139 self.storage_mock = mock.MagicMock()
140 self.header_mock = self.PatchObject(
141 cros_generate_breakpad_symbols, 'ReadSymsHeader',
142 return_value=cros_generate_breakpad_symbols.SymbolHeader(
143 os='os', cpu='cpu', id='id', name='name'))
144
145 def testNoStorageOrPaths(self):
146 """We don't want to talk to the server if there's no storage or files"""
147 upload_symbols.SymbolDeduplicator(None, [])
148 upload_symbols.SymbolDeduplicator(self.storage_mock, [])
149 self.assertEqual(self.storage_mock.call_count, 0)
150 self.assertEqual(self.header_mock.call_count, 0)
151
152 def testStorageException(self):
153 """We want to just warn & move on when dedupe server fails"""
154 log_mock = self.PatchObject(cros_build_lib, 'Warning')
155 self.storage_mock.contains.side_effect = Exception('storage error')
156 sym_paths = ['/a', '/bbbbbb', '/cc.c']
157 ret = upload_symbols.SymbolDeduplicator(self.storage_mock, sym_paths)
158 self.assertEqual(log_mock.call_count, 1)
159 self.assertEqual(self.storage_mock.contains.call_count, 1)
160 self.assertEqual(self.header_mock.call_count, len(sym_paths))
161 self.assertEqual(len(ret), len(sym_paths))
162
163
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400164class UploadSymbolTest(cros_test_lib.MockTempDirTestCase):
Mike Frysingerc4ab5782013-10-02 18:14:22 -0400165 """Tests for UploadSymbol()"""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400166
167 def setUp(self):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400168 self.sym_file = os.path.join(self.tempdir, 'foo.sym')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500169 self.sym_item = upload_symbols.FakeItem(sym_file=self.sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400170 self.url = 'http://eatit'
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500171 self.upload_mock = self.PatchObject(upload_symbols, 'SymUpload')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400172
173 def testUploadSymbolNormal(self):
174 """Verify we try to upload on a normal file"""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400175 osutils.Touch(self.sym_file)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500176 ret = upload_symbols.UploadSymbol(self.url, self.sym_item)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400177 self.assertEqual(ret, 0)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500178 self.upload_mock.assert_called_with(self.url, self.sym_item)
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500179 self.assertEqual(self.upload_mock.call_count, 1)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400180
181 def testUploadSymbolErrorCountExceeded(self):
182 """Verify that when the error count gets too high, we stop uploading"""
183 errors = ctypes.c_int(10000)
184 # Pass in garbage values so that we crash if num_errors isn't handled.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500185 ret = upload_symbols.UploadSymbol(None, self.sym_item, sleep=None,
186 num_errors=errors)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400187 self.assertEqual(ret, 0)
188
Mike Frysingerc4ab5782013-10-02 18:14:22 -0400189 def testUploadRetryErrors(self, side_effect=None):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400190 """Verify that we retry errors (and eventually give up)"""
Mike Frysingerc4ab5782013-10-02 18:14:22 -0400191 if not side_effect:
192 side_effect = urllib2.HTTPError('http://', 400, 'fail', {}, None)
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500193 self.upload_mock.side_effect = side_effect
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400194 errors = ctypes.c_int()
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500195 item = upload_symbols.FakeItem(sym_file='/dev/null')
196 ret = upload_symbols.UploadSymbol(self.url, item, num_errors=errors)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400197 self.assertEqual(ret, 1)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500198 self.upload_mock.assert_called_with(self.url, item)
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500199 self.assertTrue(self.upload_mock.call_count >= upload_symbols.MAX_RETRIES)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400200
Mike Frysingerc4ab5782013-10-02 18:14:22 -0400201 def testConnectRetryErrors(self):
202 """Verify that we retry errors (and eventually give up) w/connect errors"""
203 side_effect = urllib2.URLError('foo')
204 self.testUploadRetryErrors(side_effect=side_effect)
205
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400206 def testTruncateTooBigFiles(self):
207 """Verify we shrink big files"""
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500208 def SymUpload(_url, sym_item):
209 content = osutils.ReadFile(sym_item.sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400210 self.assertEqual(content, 'some junk\n')
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500211 self.upload_mock.upload_mock.side_effect = SymUpload
212 content = '\n'.join((
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400213 'STACK CFI 1234',
214 'some junk',
215 'STACK CFI 1234',
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500216 ))
217 osutils.WriteFile(self.sym_file, content)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500218 ret = upload_symbols.UploadSymbol(self.url, self.sym_item, file_limit=1)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400219 self.assertEqual(ret, 0)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500220 # Make sure the item passed to the upload has a temp file and not the
221 # original -- only the temp one has been stripped down.
222 temp_item = self.upload_mock.call_args[0][1]
223 self.assertNotEqual(temp_item.sym_file, self.sym_item.sym_file)
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500224 self.assertEqual(self.upload_mock.call_count, 1)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400225
226 def testTruncateReallyLargeFiles(self):
227 """Verify we try to shrink really big files"""
Mike Frysinger02e92402013-11-22 16:22:02 -0500228 warn_mock = self.PatchObject(cros_build_lib, 'PrintBuildbotStepWarnings')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400229 with open(self.sym_file, 'w+b') as f:
230 f.truncate(upload_symbols.CRASH_SERVER_FILE_LIMIT + 100)
231 f.seek(0)
232 f.write('STACK CFI 1234\n\n')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500233 ret = upload_symbols.UploadSymbol(self.url, self.sym_item)
Mike Frysinger02e92402013-11-22 16:22:02 -0500234 self.assertEqual(ret, 0)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500235 # Make sure the item passed to the upload has a temp file and not the
236 # original -- only the temp one has been truncated.
237 temp_item = self.upload_mock.call_args[0][1]
238 self.assertNotEqual(temp_item.sym_file, self.sym_item.sym_file)
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500239 self.assertEqual(self.upload_mock.call_count, 1)
Mike Frysinger02e92402013-11-22 16:22:02 -0500240 self.assertEqual(warn_mock.call_count, 1)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400241
242
Mike Frysinger094a2172013-08-14 12:54:35 -0400243class SymUploadTest(cros_test_lib.MockTempDirTestCase):
Mike Frysingerc4ab5782013-10-02 18:14:22 -0400244 """Tests for SymUpload()"""
Mike Frysinger094a2172013-08-14 12:54:35 -0400245
246 SYM_URL = 'http://localhost/post/it/here'
247 SYM_CONTENTS = """MODULE Linux arm 123-456 blkid
248PUBLIC 1471 0 main"""
249
250 def setUp(self):
251 self.sym_file = os.path.join(self.tempdir, 'test.sym')
252 osutils.WriteFile(self.sym_file, self.SYM_CONTENTS)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500253 self.sym_item = upload_symbols.SymbolItem(self.sym_file)
Mike Frysinger094a2172013-08-14 12:54:35 -0400254
255 def testPostUpload(self):
256 """Verify HTTP POST has all the fields we need"""
257 m = self.PatchObject(urllib2, 'urlopen', autospec=True)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500258 upload_symbols.SymUpload(self.SYM_URL, self.sym_item)
Mike Frysinger094a2172013-08-14 12:54:35 -0400259 self.assertEquals(m.call_count, 1)
260 req = m.call_args[0][0]
261 self.assertEquals(req.get_full_url(), self.SYM_URL)
262 data = ''.join([x for x in req.get_data()])
263
264 fields = {
265 'code_file': 'blkid',
266 'debug_file': 'blkid',
267 'debug_identifier': '123456',
268 'os': 'Linux',
269 'cpu': 'arm',
270 }
271 for key, val in fields.iteritems():
272 line = 'Content-Disposition: form-data; name="%s"\r\n' % key
273 self.assertTrue(line in data)
274 line = '%s\r\n' % val
275 self.assertTrue(line in data)
276 line = ('Content-Disposition: form-data; name="symbol_file"; '
277 'filename="test.sym"\r\n')
278 self.assertTrue(line in data)
279 self.assertTrue(self.SYM_CONTENTS in data)
280
281
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500282class UtilTest(cros_test_lib.TempDirTestCase):
283 """Various tests for utility funcs."""
284
285 def testWriteQueueToFile(self):
286 """Basic test for WriteQueueToFile."""
287 listing = os.path.join(self.tempdir, 'list')
288 exp_list = [
289 'b/c.txt',
290 'foo.log',
291 'there/might/be/giants',
292 ]
293 relpath = '/a'
294
295 q = multiprocessing.Queue()
296 for f in exp_list:
297 q.put(os.path.join(relpath, f))
298 upload_symbols.WriteQueueToFile(listing, q, '/a')
299
300 got_list = osutils.ReadFile(listing).splitlines()
301 self.assertEquals(exp_list, got_list)
302
303
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400304if __name__ == '__main__':
305 # pylint: disable=W0212
306 # Set timeouts small so that if the unit test hangs, it won't hang for long.
307 parallel._BackgroundTask.STARTUP_TIMEOUT = 5
308 parallel._BackgroundTask.EXIT_TIMEOUT = 5
309
310 # We want to test retry behavior, so make sure we don't sleep.
311 upload_symbols.INITIAL_RETRY_DELAY = 0
312
313 # Run the tests.
314 cros_test_lib.main(level=logging.INFO)