Upstream version 8.36.161.0
[platform/framework/web/crosswalk.git] / src / third_party / chromite / scripts / upload_symbols_unittest.py
1 #!/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
6 """Unittests for upload_symbols.py"""
7
8 from __future__ import print_function
9
10 import ctypes
11 import logging
12 import multiprocessing
13 import os
14 import sys
15 import urllib2
16
17 sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)),
18                                 '..', '..'))
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
26
27 # TODO(build): Finish test wrapper (http://crosbug.com/37517).
28 # Until then, this has to be after the chromite imports.
29 import isolateserver
30 import mock
31
32
33 class UploadSymbolsTest(cros_test_lib.MockTempDirTestCase):
34   """Tests for UploadSymbols()"""
35
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     self.sym_paths = [
44         'bar/real.sym',
45         'foo/real.sym',
46         'some/dir/here/real.sym',
47     ]
48
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'))
53
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'))
66
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
79     self.upload_mock.side_effect = UploadSymbol
80     with parallel_unittest.ParallelMock():
81       ret = upload_symbols.UploadSymbols('', breakpad_dir=self.tempdir, sleep=0,
82                                          retry=False)
83       self.assertEquals(ret, 4)
84
85   def testUploadCount(self):
86     """Verify we can limit the number of uploaded symbols"""
87     self.upload_mock.return_value = 0
88     for c in xrange(3):
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)
95
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)
107
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)
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.
157
158   def testDedupeNotifyFailure(self):
159     """Test that a dedupe server failure midway doesn't wedge things"""
160     api_mock = mock.MagicMock()
161
162     def _Contains(items):
163       """Do not dedupe anything"""
164       return items
165     api_mock.contains.side_effect = _Contains
166
167     # Use a list so the closure below can modify the value.
168     item_count = [0]
169     # Pick a number big enough to trigger a hang normally, but not so
170     # big it adds a lot of overhead.
171     item_limit = 50
172     def _Push(*_args):
173       """Die in the middle of the push list"""
174       item_count[0] += 1
175       if item_count[0] > (item_limit / 10):
176         raise ValueError('time to die')
177     api_mock.push.side_effect = _Push
178
179     self.PatchObject(isolateserver, 'get_storage_api', return_value=api_mock)
180
181     def _Uploader(*args, **kwargs):
182       """Pass the uploaded symbol to the deduper"""
183       sym_item = args[1]
184       passed_queue = kwargs['passed_queue']
185       passed_queue.put(sym_item)
186     self.upload_mock.side_effect = _Uploader
187
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.
195
196
197 class SymbolDeduplicatorNotifyTest(cros_test_lib.MockTestCase):
198   """Tests for SymbolDeduplicatorNotify()"""
199
200   def setUp(self):
201     self.storage_mock = self.PatchObject(isolateserver, 'get_storage_api')
202
203   def testSmoke(self):
204     """Basic run through the system."""
205     q = mock.MagicMock()
206     q.get.side_effect = (upload_symbols.FakeItem(), None,)
207     upload_symbols.SymbolDeduplicatorNotify('name', q)
208
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')
212     q = mock.MagicMock()
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)
217
218
219 class SymbolDeduplicatorTest(cros_test_lib.MockTestCase):
220   """Tests for SymbolDeduplicator()"""
221
222   def setUp(self):
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'))
228
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)
235
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))
246
247
248 class UploadSymbolTest(cros_test_lib.MockTempDirTestCase):
249   """Tests for UploadSymbol()"""
250
251   def setUp(self):
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')
256
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)
264
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,
270                                       num_errors=errors)
271     self.assertEqual(ret, 0)
272
273   def testUploadRetryErrors(self, side_effect=None):
274     """Verify that we retry errors (and eventually give up)"""
275     if not side_effect:
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)
284
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)
289
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((
297         'STACK CFI 1234',
298         'some junk',
299         'STACK CFI 1234',
300     ))
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)
309
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)
315       f.seek(0)
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)
325
326
327 class SymUploadTest(cros_test_lib.MockTempDirTestCase):
328   """Tests for SymUpload()"""
329
330   SYM_URL = 'http://localhost/post/it/here'
331   SYM_CONTENTS = """MODULE Linux arm 123-456 blkid
332 PUBLIC 1471 0 main"""
333
334   def setUp(self):
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)
338
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()])
347
348     fields = {
349         'code_file': 'blkid',
350         'debug_file': 'blkid',
351         'debug_identifier': '123456',
352         'os': 'Linux',
353         'cpu': 'arm',
354     }
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)
364
365
366 class UtilTest(cros_test_lib.TempDirTestCase):
367   """Various tests for utility funcs."""
368
369   def testWriteQueueToFile(self):
370     """Basic test for WriteQueueToFile."""
371     listing = os.path.join(self.tempdir, 'list')
372     exp_list = [
373         'b/c.txt',
374         'foo.log',
375         'there/might/be/giants',
376     ]
377     relpath = '/a'
378
379     q = multiprocessing.Queue()
380     for f in exp_list:
381       q.put(os.path.join(relpath, f))
382     q.put(None)
383     upload_symbols.WriteQueueToFile(listing, q, '/a')
384
385     got_list = osutils.ReadFile(listing).splitlines()
386     self.assertEquals(exp_list, got_list)
387
388
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
394
395   # We want to test retry behavior, so make sure we don't sleep.
396   upload_symbols.INITIAL_RETRY_DELAY = 0
397
398   # Run the tests.
399   cros_test_lib.main(level=logging.INFO)