blob: 462ab9be92bdddba8da5dba5dee06b2a4fc5382d [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 Frysinger0a2fd922014-09-12 20:23:42 -070010import BaseHTTPServer
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040011import ctypes
Mike Frysinger06da5202014-09-26 17:30:33 -050012import errno
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040013import logging
Mike Frysinger0c0efa22014-02-09 23:32:23 -050014import multiprocessing
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040015import os
Mike Frysinger0a2fd922014-09-12 20:23:42 -070016import signal
Mike Frysinger06da5202014-09-26 17:30:33 -050017import socket
Mike Frysinger0a2fd922014-09-12 20:23:42 -070018import SocketServer
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040019import sys
Aviv Keshetd1f04632014-05-09 11:33:46 -070020import time
Mike Frysinger094a2172013-08-14 12:54:35 -040021import urllib2
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040022
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
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040030sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)),
31 '..', '..'))
Mike Frysinger02e92402013-11-22 16:22:02 -050032from chromite.lib import cros_build_lib
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040033from chromite.lib import cros_test_lib
34from chromite.lib import osutils
35from chromite.lib import parallel
36from chromite.lib import parallel_unittest
Mike Frysinger0a2fd922014-09-12 20:23:42 -070037from chromite.lib import remote_access
Mike Frysinger5e30a4b2014-02-12 20:23:04 -050038from chromite.scripts import cros_generate_breakpad_symbols
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040039from chromite.scripts import upload_symbols
40
Mike Frysinger0c0efa22014-02-09 23:32:23 -050041# TODO(build): Finish test wrapper (http://crosbug.com/37517).
42# Until then, this has to be after the chromite imports.
43import isolateserver
44import mock
45
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040046
Mike Frysinger0a2fd922014-09-12 20:23:42 -070047class SymbolServerRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
48 """HTTP handler for symbol POSTs"""
49
50 RESP_CODE = None
51 RESP_MSG = None
52
53 def do_POST(self):
54 """Handle a POST request"""
55 # Drain the data from the client. If we don't, we might write the response
56 # and close the socket before the client finishes, so they die with EPIPE.
57 clen = int(self.headers.get('Content-Length', '0'))
58 self.rfile.read(clen)
59
60 self.send_response(self.RESP_CODE, self.RESP_MSG)
61 self.end_headers()
62
63 def log_message(self, *args, **kwargs):
64 """Stub the logger as it writes to stderr"""
65 pass
66
67
68class SymbolServer(SocketServer.ThreadingTCPServer, BaseHTTPServer.HTTPServer):
69 """Simple HTTP server that forks each request"""
70
71
72class UploadSymbolsServerTest(cros_test_lib.MockTempDirTestCase):
73 """Tests for UploadSymbols() and a local HTTP server"""
74
75 SYM_CONTENTS = """MODULE Linux arm 123-456 blkid
76PUBLIC 1471 0 main"""
77
78 def SpawnServer(self, RequestHandler):
79 """Spawn a new http server"""
Mike Frysinger06da5202014-09-26 17:30:33 -050080 while True:
81 try:
82 port = remote_access.GetUnusedPort()
83 address = ('', port)
84 self.httpd = SymbolServer(address, RequestHandler)
85 break
86 except socket.error as e:
87 if e.errno == errno.EADDRINUSE:
88 continue
89 raise
Mike Frysinger0a2fd922014-09-12 20:23:42 -070090 self.server = 'http://localhost:%i' % port
Mike Frysinger0a2fd922014-09-12 20:23:42 -070091 self.httpd_pid = os.fork()
92 if self.httpd_pid == 0:
93 self.httpd.serve_forever(poll_interval=0.1)
94 sys.exit(0)
95
96 def setUp(self):
97 self.httpd_pid = None
98 self.httpd = None
99 self.server = None
100 self.sym_file = os.path.join(self.tempdir, 'test.sym')
101 osutils.WriteFile(self.sym_file, self.SYM_CONTENTS)
102
103 def tearDown(self):
104 # Only kill the server if we forked one.
105 if self.httpd_pid:
106 os.kill(self.httpd_pid, signal.SIGUSR1)
107
108 def testSuccess(self):
109 """The server returns success for all uploads"""
110 class Handler(SymbolServerRequestHandler):
111 """Always return 200"""
112 RESP_CODE = 200
113
114 self.SpawnServer(Handler)
115 ret = upload_symbols.UploadSymbols('', server=self.server, sleep=0,
116 sym_paths=[self.sym_file] * 10,
117 retry=False)
118 self.assertEqual(ret, 0)
119
120 def testError(self):
121 """The server returns errors for all uploads"""
122 class Handler(SymbolServerRequestHandler):
123 """Always return 500"""
124 RESP_CODE = 500
125 RESP_MSG = 'Internal Server Error'
126
127 self.SpawnServer(Handler)
128 ret = upload_symbols.UploadSymbols('', server=self.server, sleep=0,
129 sym_paths=[self.sym_file] * 10,
130 retry=False)
131 self.assertEqual(ret, 4)
132
133 def testHungServer(self):
134 """The server chokes, but we recover"""
135 class Handler(SymbolServerRequestHandler):
136 """All connections choke forever"""
137 def do_POST(self):
138 while True:
139 time.sleep(1000)
140
141 self.SpawnServer(Handler)
142 with mock.patch.object(upload_symbols, 'GetUploadTimeout') as m:
143 m.return_value = 0.1
144 ret = upload_symbols.UploadSymbols('', server=self.server, sleep=0,
145 sym_paths=[self.sym_file] * 10,
146 retry=False)
147 self.assertEqual(ret, 4)
148
149
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400150class UploadSymbolsTest(cros_test_lib.MockTempDirTestCase):
Mike Frysingerc4ab5782013-10-02 18:14:22 -0400151 """Tests for UploadSymbols()"""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400152
153 def setUp(self):
154 for d in ('foo', 'bar', 'some/dir/here'):
155 d = os.path.join(self.tempdir, d)
156 osutils.SafeMakedirs(d)
157 for f in ('ignored', 'real.sym', 'no.sym.here'):
158 f = os.path.join(d, f)
159 osutils.Touch(f)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500160 self.sym_paths = [
161 'bar/real.sym',
162 'foo/real.sym',
163 'some/dir/here/real.sym',
164 ]
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400165
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500166 self.upload_mock = self.PatchObject(upload_symbols, 'UploadSymbol')
167 self.PatchObject(cros_generate_breakpad_symbols, 'ReadSymsHeader',
168 return_value=cros_generate_breakpad_symbols.SymbolHeader(
169 os='os', cpu='cpu', id='id', name='name'))
170
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400171 def _testUploadURL(self, official, expected_url):
172 """Helper for checking the url used"""
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500173 self.upload_mock.return_value = 0
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400174 with parallel_unittest.ParallelMock():
Mike Frysinger02e92402013-11-22 16:22:02 -0500175 ret = upload_symbols.UploadSymbols('', official=official, retry=False,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400176 breakpad_dir=self.tempdir, sleep=0)
177 self.assertEqual(ret, 0)
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500178 self.assertEqual(self.upload_mock.call_count, 3)
179 for call_args in self.upload_mock.call_args_list:
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500180 url, sym_item = call_args[0]
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400181 self.assertEqual(url, expected_url)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500182 self.assertTrue(sym_item.sym_file.endswith('.sym'))
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400183
184 def testOfficialUploadURL(self):
185 """Verify we upload to the real crash server for official builds"""
186 self._testUploadURL(True, upload_symbols.OFFICIAL_UPLOAD_URL)
187
188 def testUnofficialUploadURL(self):
189 """Verify we upload to the staging crash server for unofficial builds"""
190 self._testUploadURL(False, upload_symbols.STAGING_UPLOAD_URL)
191
192 def testUploadSymbolFailureSimple(self):
193 """Verify that when UploadSymbol fails, the error count is passed up"""
194 def UploadSymbol(*_args, **kwargs):
195 kwargs['num_errors'].value = 4
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500196 self.upload_mock.side_effect = UploadSymbol
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400197 with parallel_unittest.ParallelMock():
Mike Frysinger02e92402013-11-22 16:22:02 -0500198 ret = upload_symbols.UploadSymbols('', breakpad_dir=self.tempdir, sleep=0,
199 retry=False)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400200 self.assertEquals(ret, 4)
201
202 def testUploadCount(self):
203 """Verify we can limit the number of uploaded symbols"""
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500204 self.upload_mock.return_value = 0
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400205 for c in xrange(3):
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500206 self.upload_mock.reset_mock()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400207 with parallel_unittest.ParallelMock():
208 ret = upload_symbols.UploadSymbols('', breakpad_dir=self.tempdir,
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500209 sleep=0, upload_limit=c)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400210 self.assertEquals(ret, 0)
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500211 self.assertEqual(self.upload_mock.call_count, c)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400212
Mike Frysinger7f9be142014-01-15 02:16:42 -0500213 def testFailedFileList(self):
214 """Verify the failed file list is populated with the right content"""
215 def UploadSymbol(*args, **kwargs):
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500216 kwargs['failed_queue'].put(args[1].sym_file)
Mike Frysinger7f9be142014-01-15 02:16:42 -0500217 kwargs['num_errors'].value = 4
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500218 self.upload_mock.side_effect = UploadSymbol
Mike Frysinger7f9be142014-01-15 02:16:42 -0500219 with parallel_unittest.ParallelMock():
220 failed_list = os.path.join(self.tempdir, 'list')
221 ret = upload_symbols.UploadSymbols('', breakpad_dir=self.tempdir, sleep=0,
222 retry=False, failed_list=failed_list)
223 self.assertEquals(ret, 4)
224
225 # Need to sort the output as parallel/fs discovery can be unordered.
226 got_list = sorted(osutils.ReadFile(failed_list).splitlines())
Mike Frysingerd41938e2014-02-10 06:37:55 -0500227 self.assertEquals(self.sym_paths, got_list)
228
229 def _testUpload(self, inputs, sym_paths=None):
230 """Helper for testing uploading of specific paths"""
231 if sym_paths is None:
232 sym_paths = inputs
233
234 self.upload_mock.return_value = 0
235 with parallel_unittest.ParallelMock():
236 ret = upload_symbols.UploadSymbols(sym_paths=inputs, sleep=0,
237 retry=False)
238 self.assertEquals(ret, 0)
239 self.assertEquals(self.upload_mock.call_count, len(sym_paths))
240
241 # Since upload order is arbitrary, we have to do a manual scan for each
242 # path ourselves against the uploaded file list.
243 found_syms = [x[0][1].sym_file for x in self.upload_mock.call_args_list]
244 for found_sym in found_syms:
245 for path in sym_paths:
246 if found_sym.endswith(path):
247 break
248 else:
249 raise AssertionError('Could not locate %s in %r' % (path, found_syms))
250
251 def testUploadFiles(self):
252 """Test uploading specific symbol files"""
253 sym_paths = (
254 os.path.join(self.tempdir, 'bar', 'real.sym'),
255 os.path.join(self.tempdir, 'foo', 'real.sym'),
256 )
257 self._testUpload(sym_paths)
258
259 def testUploadDirectory(self):
260 """Test uploading directory of symbol files"""
261 self._testUpload([self.tempdir], sym_paths=self.sym_paths)
262
263 def testUploadLocalTarball(self):
264 """Test uploading symbols contains in a local tarball"""
265 tarball = os.path.join(self.tempdir, 'syms.tar.gz')
266 cros_build_lib.CreateTarball(
267 'syms.tar.gz', self.tempdir, compression=cros_build_lib.COMP_GZIP,
268 inputs=('foo', 'bar', 'some'))
269 self._testUpload([tarball], sym_paths=self.sym_paths)
270
271 def testUploadRemoteTarball(self):
272 """Test uploading symbols contains in a remote tarball"""
273 # TODO: Need to figure out how to mock out lib.cache.TarballCache.
Mike Frysinger7f9be142014-01-15 02:16:42 -0500274
Mike Frysinger58312e92014-03-18 04:18:36 -0400275 def testDedupeNotifyFailure(self):
276 """Test that a dedupe server failure midway doesn't wedge things"""
277 api_mock = mock.MagicMock()
278
279 def _Contains(items):
280 """Do not dedupe anything"""
281 return items
282 api_mock.contains.side_effect = _Contains
283
284 # Use a list so the closure below can modify the value.
285 item_count = [0]
286 # Pick a number big enough to trigger a hang normally, but not so
287 # big it adds a lot of overhead.
288 item_limit = 50
289 def _Push(*_args):
290 """Die in the middle of the push list"""
291 item_count[0] += 1
292 if item_count[0] > (item_limit / 10):
293 raise ValueError('time to die')
294 api_mock.push.side_effect = _Push
295
296 self.PatchObject(isolateserver, 'get_storage_api', return_value=api_mock)
297
298 def _Uploader(*args, **kwargs):
299 """Pass the uploaded symbol to the deduper"""
300 sym_item = args[1]
301 passed_queue = kwargs['passed_queue']
302 passed_queue.put(sym_item)
303 self.upload_mock.side_effect = _Uploader
304
305 self.upload_mock.return_value = 0
306 with parallel_unittest.ParallelMock():
307 ret = upload_symbols.UploadSymbols(
308 '', sym_paths=[self.tempdir] * item_limit, sleep=0,
309 dedupe_namespace='inva!id name$pace')
310 self.assertEqual(ret, 0)
311 # This test normally passes by not hanging.
312
Aviv Keshetd1f04632014-05-09 11:33:46 -0700313 def testSlowDedupeSystem(self):
314 """Verify a slow-to-join process doesn't break things when dedupe is off"""
315 # The sleep value here is inherently a little racy, but seems to be good
316 # enough to trigger the bug on a semi-regular basis on developer systems.
317 self.PatchObject(upload_symbols, 'SymbolDeduplicatorNotify',
318 side_effect=lambda *args: time.sleep(1))
319 # Test passing means the code didn't throw an exception.
320 upload_symbols.UploadSymbols(sym_paths=[self.tempdir])
321
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400322
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500323class SymbolDeduplicatorNotifyTest(cros_test_lib.MockTestCase):
324 """Tests for SymbolDeduplicatorNotify()"""
325
326 def setUp(self):
327 self.storage_mock = self.PatchObject(isolateserver, 'get_storage_api')
328
329 def testSmoke(self):
330 """Basic run through the system."""
331 q = mock.MagicMock()
332 q.get.side_effect = (upload_symbols.FakeItem(), None,)
333 upload_symbols.SymbolDeduplicatorNotify('name', q)
334
335 def testStorageException(self):
336 """We want to just warn & move on when dedupe server fails"""
337 log_mock = self.PatchObject(cros_build_lib, 'Warning')
338 q = mock.MagicMock()
339 q.get.side_effect = (upload_symbols.FakeItem(), None,)
340 self.storage_mock.side_effect = Exception
341 upload_symbols.SymbolDeduplicatorNotify('name', q)
342 self.assertEqual(log_mock.call_count, 1)
343
344
345class SymbolDeduplicatorTest(cros_test_lib.MockTestCase):
346 """Tests for SymbolDeduplicator()"""
347
348 def setUp(self):
349 self.storage_mock = mock.MagicMock()
350 self.header_mock = self.PatchObject(
351 cros_generate_breakpad_symbols, 'ReadSymsHeader',
352 return_value=cros_generate_breakpad_symbols.SymbolHeader(
353 os='os', cpu='cpu', id='id', name='name'))
354
355 def testNoStorageOrPaths(self):
356 """We don't want to talk to the server if there's no storage or files"""
357 upload_symbols.SymbolDeduplicator(None, [])
358 upload_symbols.SymbolDeduplicator(self.storage_mock, [])
359 self.assertEqual(self.storage_mock.call_count, 0)
360 self.assertEqual(self.header_mock.call_count, 0)
361
362 def testStorageException(self):
363 """We want to just warn & move on when dedupe server fails"""
364 log_mock = self.PatchObject(cros_build_lib, 'Warning')
365 self.storage_mock.contains.side_effect = Exception('storage error')
366 sym_paths = ['/a', '/bbbbbb', '/cc.c']
367 ret = upload_symbols.SymbolDeduplicator(self.storage_mock, sym_paths)
368 self.assertEqual(log_mock.call_count, 1)
369 self.assertEqual(self.storage_mock.contains.call_count, 1)
370 self.assertEqual(self.header_mock.call_count, len(sym_paths))
371 self.assertEqual(len(ret), len(sym_paths))
372
373
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400374class UploadSymbolTest(cros_test_lib.MockTempDirTestCase):
Mike Frysingerc4ab5782013-10-02 18:14:22 -0400375 """Tests for UploadSymbol()"""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400376
377 def setUp(self):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400378 self.sym_file = os.path.join(self.tempdir, 'foo.sym')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500379 self.sym_item = upload_symbols.FakeItem(sym_file=self.sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400380 self.url = 'http://eatit'
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500381 self.upload_mock = self.PatchObject(upload_symbols, 'SymUpload')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400382
383 def testUploadSymbolNormal(self):
384 """Verify we try to upload on a normal file"""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400385 osutils.Touch(self.sym_file)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500386 ret = upload_symbols.UploadSymbol(self.url, self.sym_item)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400387 self.assertEqual(ret, 0)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500388 self.upload_mock.assert_called_with(self.url, self.sym_item)
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500389 self.assertEqual(self.upload_mock.call_count, 1)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400390
391 def testUploadSymbolErrorCountExceeded(self):
392 """Verify that when the error count gets too high, we stop uploading"""
393 errors = ctypes.c_int(10000)
394 # Pass in garbage values so that we crash if num_errors isn't handled.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500395 ret = upload_symbols.UploadSymbol(None, self.sym_item, sleep=None,
396 num_errors=errors)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400397 self.assertEqual(ret, 0)
398
Mike Frysingerc4ab5782013-10-02 18:14:22 -0400399 def testUploadRetryErrors(self, side_effect=None):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400400 """Verify that we retry errors (and eventually give up)"""
Mike Frysingerc4ab5782013-10-02 18:14:22 -0400401 if not side_effect:
402 side_effect = urllib2.HTTPError('http://', 400, 'fail', {}, None)
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500403 self.upload_mock.side_effect = side_effect
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400404 errors = ctypes.c_int()
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500405 item = upload_symbols.FakeItem(sym_file='/dev/null')
406 ret = upload_symbols.UploadSymbol(self.url, item, num_errors=errors)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400407 self.assertEqual(ret, 1)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500408 self.upload_mock.assert_called_with(self.url, item)
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500409 self.assertTrue(self.upload_mock.call_count >= upload_symbols.MAX_RETRIES)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400410
Mike Frysingerc4ab5782013-10-02 18:14:22 -0400411 def testConnectRetryErrors(self):
412 """Verify that we retry errors (and eventually give up) w/connect errors"""
413 side_effect = urllib2.URLError('foo')
414 self.testUploadRetryErrors(side_effect=side_effect)
415
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400416 def testTruncateTooBigFiles(self):
417 """Verify we shrink big files"""
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500418 def SymUpload(_url, sym_item):
419 content = osutils.ReadFile(sym_item.sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400420 self.assertEqual(content, 'some junk\n')
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500421 self.upload_mock.upload_mock.side_effect = SymUpload
422 content = '\n'.join((
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400423 'STACK CFI 1234',
424 'some junk',
425 'STACK CFI 1234',
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500426 ))
427 osutils.WriteFile(self.sym_file, content)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500428 ret = upload_symbols.UploadSymbol(self.url, self.sym_item, file_limit=1)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400429 self.assertEqual(ret, 0)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500430 # Make sure the item passed to the upload has a temp file and not the
431 # original -- only the temp one has been stripped down.
432 temp_item = self.upload_mock.call_args[0][1]
433 self.assertNotEqual(temp_item.sym_file, self.sym_item.sym_file)
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500434 self.assertEqual(self.upload_mock.call_count, 1)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400435
436 def testTruncateReallyLargeFiles(self):
437 """Verify we try to shrink really big files"""
Mike Frysinger02e92402013-11-22 16:22:02 -0500438 warn_mock = self.PatchObject(cros_build_lib, 'PrintBuildbotStepWarnings')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400439 with open(self.sym_file, 'w+b') as f:
440 f.truncate(upload_symbols.CRASH_SERVER_FILE_LIMIT + 100)
441 f.seek(0)
442 f.write('STACK CFI 1234\n\n')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500443 ret = upload_symbols.UploadSymbol(self.url, self.sym_item)
Mike Frysinger02e92402013-11-22 16:22:02 -0500444 self.assertEqual(ret, 0)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500445 # Make sure the item passed to the upload has a temp file and not the
446 # original -- only the temp one has been truncated.
447 temp_item = self.upload_mock.call_args[0][1]
448 self.assertNotEqual(temp_item.sym_file, self.sym_item.sym_file)
Mike Frysinger5e30a4b2014-02-12 20:23:04 -0500449 self.assertEqual(self.upload_mock.call_count, 1)
Mike Frysinger02e92402013-11-22 16:22:02 -0500450 self.assertEqual(warn_mock.call_count, 1)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400451
452
Mike Frysinger094a2172013-08-14 12:54:35 -0400453class SymUploadTest(cros_test_lib.MockTempDirTestCase):
Mike Frysingerc4ab5782013-10-02 18:14:22 -0400454 """Tests for SymUpload()"""
Mike Frysinger094a2172013-08-14 12:54:35 -0400455
456 SYM_URL = 'http://localhost/post/it/here'
457 SYM_CONTENTS = """MODULE Linux arm 123-456 blkid
458PUBLIC 1471 0 main"""
459
460 def setUp(self):
461 self.sym_file = os.path.join(self.tempdir, 'test.sym')
462 osutils.WriteFile(self.sym_file, self.SYM_CONTENTS)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500463 self.sym_item = upload_symbols.SymbolItem(self.sym_file)
Mike Frysinger094a2172013-08-14 12:54:35 -0400464
465 def testPostUpload(self):
466 """Verify HTTP POST has all the fields we need"""
467 m = self.PatchObject(urllib2, 'urlopen', autospec=True)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500468 upload_symbols.SymUpload(self.SYM_URL, self.sym_item)
Mike Frysinger094a2172013-08-14 12:54:35 -0400469 self.assertEquals(m.call_count, 1)
470 req = m.call_args[0][0]
471 self.assertEquals(req.get_full_url(), self.SYM_URL)
472 data = ''.join([x for x in req.get_data()])
473
474 fields = {
475 'code_file': 'blkid',
476 'debug_file': 'blkid',
477 'debug_identifier': '123456',
478 'os': 'Linux',
479 'cpu': 'arm',
480 }
481 for key, val in fields.iteritems():
482 line = 'Content-Disposition: form-data; name="%s"\r\n' % key
483 self.assertTrue(line in data)
484 line = '%s\r\n' % val
485 self.assertTrue(line in data)
486 line = ('Content-Disposition: form-data; name="symbol_file"; '
487 'filename="test.sym"\r\n')
488 self.assertTrue(line in data)
489 self.assertTrue(self.SYM_CONTENTS in data)
490
Mike Frysinger71046662014-09-12 18:15:15 -0700491 def testTimeout(self):
492 """Verify timeouts scale based on filesize"""
493 m = self.PatchObject(urllib2, 'urlopen', autospec=True)
494 size = self.PatchObject(os.path, 'getsize')
495
496 tests = (
497 # Small files should get rounded up to the minimum timeout.
498 (10, upload_symbols.UPLOAD_MIN_TIMEOUT),
499 # A 50MiB file should take like ~4 minutes.
500 (50 * 1024 * 1024, 257),
501 )
502 for size.return_value, timeout in tests:
503 upload_symbols.SymUpload(self.SYM_URL, self.sym_item)
504 self.assertEqual(m.call_args[1]['timeout'], timeout)
505
Mike Frysinger094a2172013-08-14 12:54:35 -0400506
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500507class UtilTest(cros_test_lib.TempDirTestCase):
508 """Various tests for utility funcs."""
509
510 def testWriteQueueToFile(self):
511 """Basic test for WriteQueueToFile."""
512 listing = os.path.join(self.tempdir, 'list')
513 exp_list = [
514 'b/c.txt',
515 'foo.log',
516 'there/might/be/giants',
517 ]
518 relpath = '/a'
519
520 q = multiprocessing.Queue()
521 for f in exp_list:
522 q.put(os.path.join(relpath, f))
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500523 q.put(None)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500524 upload_symbols.WriteQueueToFile(listing, q, '/a')
525
526 got_list = osutils.ReadFile(listing).splitlines()
527 self.assertEquals(exp_list, got_list)
528
529
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400530if __name__ == '__main__':
531 # pylint: disable=W0212
532 # Set timeouts small so that if the unit test hangs, it won't hang for long.
533 parallel._BackgroundTask.STARTUP_TIMEOUT = 5
534 parallel._BackgroundTask.EXIT_TIMEOUT = 5
535
536 # We want to test retry behavior, so make sure we don't sleep.
537 upload_symbols.INITIAL_RETRY_DELAY = 0
538
539 # Run the tests.
540 cros_test_lib.main(level=logging.INFO)