blob: ec4b6d8830079d77fa14458907f631c4cf3a47ca [file] [log] [blame]
Mike Frysingerf1ba7ad2022-09-12 05:42:57 -04001# Copyright 2013 The ChromiumOS Authors
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -04002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Mike Frysingereb753bf2013-11-22 16:05:35 -05005"""Unittests for upload_symbols.py"""
6
Mike Frysinger06da5202014-09-26 17:30:33 -05007import errno
Mike Frysingere852b072021-05-21 12:39:03 -04008import http.server
Don Garretta28be6d2016-06-16 18:09:35 -07009import itertools
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040010import os
Mike Frysinger0a2fd922014-09-12 20:23:42 -070011import signal
Mike Frysinger06da5202014-09-26 17:30:33 -050012import socket
Mike Frysingere852b072021-05-21 12:39:03 -040013import socketserver
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040014import sys
Aviv Keshetd1f04632014-05-09 11:33:46 -070015import time
Mike Frysinger166fea02021-02-12 05:30:33 -050016from unittest import mock
Mike Frysingere852b072021-05-21 12:39:03 -040017import urllib.request
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040018
Mike Frysinger40ffb532021-02-12 07:36:08 -050019import pytest # pylint: disable=import-error
Mike Frysinger40ffb532021-02-12 07:36:08 -050020
Mike Frysinger6db648e2018-07-24 19:57:58 -040021
Mike Frysinger079863c2014-10-09 23:16:46 -040022# We specifically set up a local server to connect to, so make sure we
23# delete any proxy settings that might screw that up. We also need to
24# do it here because modules that are imported below will implicitly
25# initialize with this proxy setting rather than dynamically pull it
26# on the fly :(.
Mike Frysinger92bdef52019-08-21 21:05:13 -040027# pylint: disable=wrong-import-position
Alex Klein1699fab2022-09-08 08:46:06 -060028os.environ.pop("http_proxy", None)
Mike Frysinger079863c2014-10-09 23:16:46 -040029
Aviv Keshetb7519e12016-10-04 00:50:00 -070030from chromite.lib import constants
Mike Frysingerbbd1f112016-09-08 18:25:11 -040031
Mike Frysinger40ffb532021-02-12 07:36:08 -050032
Mike Frysingerbbd1f112016-09-08 18:25:11 -040033# The isolateserver includes a bunch of third_party python packages that clash
34# with chromite's bundled third_party python packages (like oauth2client).
35# Since upload_symbols is not imported in to other parts of chromite, and there
36# are no deps in third_party we care about, purge the chromite copy. This way
37# we can use isolateserver for deduping.
38# TODO: If we ever sort out third_party/ handling and make it per-script opt-in,
39# we can purge this logic.
Mike Frysingera69df982023-03-21 16:52:27 -040040third_party = str(constants.CHROMITE_DIR / "third_party")
Mike Frysingerbbd1f112016-09-08 18:25:11 -040041while True:
Alex Klein1699fab2022-09-08 08:46:06 -060042 try:
43 sys.path.remove(third_party)
44 except ValueError:
45 break
Mike Frysingerbbd1f112016-09-08 18:25:11 -040046del third_party
47
Chris McDonaldb55b7032021-06-17 16:41:32 -060048from chromite.cbuildbot import cbuildbot_alerts
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040049from chromite.lib import cros_test_lib
50from chromite.lib import osutils
51from chromite.lib import parallel
Mike Frysinger0a2fd922014-09-12 20:23:42 -070052from chromite.lib import remote_access
Mike Frysinger5e30a4b2014-02-12 20:23:04 -050053from chromite.scripts import cros_generate_breakpad_symbols
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040054from chromite.scripts import upload_symbols
55
Mike Frysinger0c0efa22014-02-09 23:32:23 -050056
Don Garretta28be6d2016-06-16 18:09:35 -070057class SymbolsTestBase(cros_test_lib.MockTempDirTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -060058 """Base class for most symbols tests."""
Don Garretta28be6d2016-06-16 18:09:35 -070059
Alex Klein1699fab2022-09-08 08:46:06 -060060 SLIM_CONTENT = """
Don Garretta28be6d2016-06-16 18:09:35 -070061some junk
62"""
63
Alex Klein1699fab2022-09-08 08:46:06 -060064 FAT_CONTENT = """
Don Garretta28be6d2016-06-16 18:09:35 -070065STACK CFI 1234
66some junk
67STACK CFI 1234
68"""
69
Alex Klein1699fab2022-09-08 08:46:06 -060070 def setUp(self):
71 # Make certain we don't use the network.
72 self.urlopen_mock = self.PatchObject(urllib.request, "urlopen")
73 self.request_mock = self.PatchObject(
74 upload_symbols,
75 "ExecRequest",
76 return_value={"uploadUrl": "testurl", "uploadKey": "asdgasgas"},
77 )
Don Garretta28be6d2016-06-16 18:09:35 -070078
Alex Klein1699fab2022-09-08 08:46:06 -060079 # Make 'uploads' go fast.
80 self.PatchObject(upload_symbols, "SLEEP_DELAY", 0)
81 self.PatchObject(upload_symbols, "INITIAL_RETRY_DELAY", 0)
Don Garretta28be6d2016-06-16 18:09:35 -070082
Alex Klein1699fab2022-09-08 08:46:06 -060083 # So our symbol file content doesn't have to be real.
84 self.PatchObject(
85 cros_generate_breakpad_symbols,
86 "ReadSymsHeader",
87 return_value=cros_generate_breakpad_symbols.SymbolHeader(
88 os="os", cpu="cpu", id="id", name="name"
89 ),
90 )
Don Garretta28be6d2016-06-16 18:09:35 -070091
Alex Klein1699fab2022-09-08 08:46:06 -060092 self.working = os.path.join(self.tempdir, "expand")
93 osutils.SafeMakedirs(self.working)
Don Garretta28be6d2016-06-16 18:09:35 -070094
Alex Klein1699fab2022-09-08 08:46:06 -060095 self.data = os.path.join(self.tempdir, "data")
96 osutils.SafeMakedirs(self.data)
Don Garretta28be6d2016-06-16 18:09:35 -070097
Alex Klein1699fab2022-09-08 08:46:06 -060098 def createSymbolFile(
99 self, filename, content=FAT_CONTENT, size=0, status=None, dedupe=False
100 ):
101 fullname = os.path.join(self.data, filename)
102 osutils.SafeMakedirs(os.path.dirname(fullname))
Don Garretta28be6d2016-06-16 18:09:35 -0700103
Alex Kleind7197402023-04-05 13:05:29 -0600104 # If a file size is given, force that to be the minimum file size.
105 # Create a sparse file so large files are practical.
Alex Klein1699fab2022-09-08 08:46:06 -0600106 with open(fullname, "w+b") as f:
107 f.truncate(size)
108 f.seek(0)
109 f.write(content.encode("utf-8"))
Don Garretta28be6d2016-06-16 18:09:35 -0700110
Alex Klein1699fab2022-09-08 08:46:06 -0600111 result = upload_symbols.SymbolFile(
112 display_path=filename, file_name=fullname
113 )
Don Garretta28be6d2016-06-16 18:09:35 -0700114
Alex Klein1699fab2022-09-08 08:46:06 -0600115 if status:
116 result.status = status
Don Garretta28be6d2016-06-16 18:09:35 -0700117
Alex Klein1699fab2022-09-08 08:46:06 -0600118 if dedupe:
119 result.dedupe_item = upload_symbols.DedupeItem(result)
120 result.dedupe_push_state = "push_state"
Don Garretta28be6d2016-06-16 18:09:35 -0700121
Alex Klein1699fab2022-09-08 08:46:06 -0600122 return result
Don Garretta28be6d2016-06-16 18:09:35 -0700123
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400124
Mike Frysingere852b072021-05-21 12:39:03 -0400125class SymbolServerRequestHandler(http.server.BaseHTTPRequestHandler):
Alex Klein1699fab2022-09-08 08:46:06 -0600126 """HTTP handler for symbol POSTs"""
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700127
Alex Klein1699fab2022-09-08 08:46:06 -0600128 RESP_CODE = None
129 RESP_MSG = None
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700130
Alex Klein1699fab2022-09-08 08:46:06 -0600131 def do_POST(self):
132 """Handle a POST request"""
Alex Kleind7197402023-04-05 13:05:29 -0600133 # Drain the data from the client. If we don't, we might write the
134 # response and close the socket before the client finishes, so they die
135 # with EPIPE.
Alex Klein1699fab2022-09-08 08:46:06 -0600136 clen = int(self.headers.get("Content-Length", "0"))
137 self.rfile.read(clen)
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700138
Alex Klein1699fab2022-09-08 08:46:06 -0600139 self.send_response(self.RESP_CODE, self.RESP_MSG)
140 self.end_headers()
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700141
Alex Klein1699fab2022-09-08 08:46:06 -0600142 # pylint: disable=arguments-differ
143 def log_message(self, *args, **kwargs):
144 """Stub the logger as it writes to stderr"""
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700145
146
Mike Frysingere852b072021-05-21 12:39:03 -0400147class SymbolServer(socketserver.ThreadingTCPServer, http.server.HTTPServer):
Alex Klein1699fab2022-09-08 08:46:06 -0600148 """Simple HTTP server that forks each request"""
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700149
150
Alex Klein1699fab2022-09-08 08:46:06 -0600151@pytest.mark.usefixtures("singleton_manager")
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700152class UploadSymbolsServerTest(cros_test_lib.MockTempDirTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600153 """Tests for UploadSymbols() and a local HTTP server"""
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700154
Alex Klein1699fab2022-09-08 08:46:06 -0600155 SYM_CONTENTS = """MODULE Linux arm 123-456 blkid
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700156PUBLIC 1471 0 main"""
157
Alex Klein1699fab2022-09-08 08:46:06 -0600158 def SpawnServer(self, RequestHandler):
159 """Spawn a new http server"""
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700160 while True:
Alex Klein1699fab2022-09-08 08:46:06 -0600161 try:
162 port = remote_access.GetUnusedPort()
163 address = ("", port)
164 self.httpd = SymbolServer(address, RequestHandler)
165 break
166 except socket.error as e:
167 if e.errno == errno.EADDRINUSE:
168 continue
169 raise
170 self.server_url = "http://localhost:%i/post/path" % port
171 self.httpd_pid = os.fork()
172 if self.httpd_pid == 0:
173 self.httpd.serve_forever(poll_interval=0.1)
174 sys.exit(0)
175 # The child runs the server, so close the socket in the parent.
176 self.httpd.server_close()
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700177
Alex Klein1699fab2022-09-08 08:46:06 -0600178 def setUp(self):
179 self.httpd_pid = None
180 self.httpd = None
181 self.server_url = None
182 self.sym_file = os.path.join(self.tempdir, "test.sym")
183 osutils.WriteFile(self.sym_file, self.SYM_CONTENTS)
184
185 # Stop sleeps and retries for these tests.
186 self.PatchObject(upload_symbols, "SLEEP_DELAY", 0)
187 self.PatchObject(upload_symbols, "INITIAL_RETRY_DELAY", 0)
188 self.PatchObject(upload_symbols, "MAX_RETRIES", 0)
189
190 def tearDown(self):
191 # Only kill the server if we forked one.
192 if self.httpd_pid:
193 os.kill(self.httpd_pid, signal.SIGUSR1)
194
195 def testSuccess(self):
196 """The server returns success for all uploads"""
197
198 class Handler(SymbolServerRequestHandler):
199 """Always return 200"""
200
201 RESP_CODE = 200
202 self.PatchObject(
203 upload_symbols,
204 "ExecRequest",
205 return_value={
206 "uploadUrl": "testurl",
207 "uploadKey": "testSuccess",
208 },
209 )
210
211 self.SpawnServer(Handler)
212 ret = upload_symbols.UploadSymbols(
213 sym_paths=[self.sym_file] * 10,
214 upload_url=self.server_url,
215 api_key="testSuccess",
216 )
217 self.assertEqual(ret, 0)
218
219 def testError(self):
220 """The server returns errors for all uploads"""
221
222 class Handler(SymbolServerRequestHandler):
223 """All connections error"""
224
225 RESP_CODE = 500
226 RESP_MSG = "Internal Server Error"
227
228 self.SpawnServer(Handler)
229 ret = upload_symbols.UploadSymbols(
230 sym_paths=[self.sym_file] * 10,
231 upload_url=self.server_url,
232 api_key="testkey",
233 )
234 self.assertEqual(ret, 10)
235
236 def testHungServer(self):
237 """The server chokes, but we recover"""
238
239 class Handler(SymbolServerRequestHandler):
240 """All connections choke forever"""
241
242 self.PatchObject(
243 upload_symbols, "ExecRequest", return_value={"pairs": []}
244 )
245
246 def do_POST(self):
247 while True:
248 time.sleep(1000)
249
250 self.SpawnServer(Handler)
251 with mock.patch.object(upload_symbols, "GetUploadTimeout") as m:
252 m.return_value = 0.01
253 ret = upload_symbols.UploadSymbols(
254 sym_paths=[self.sym_file] * 10,
255 upload_url=self.server_url,
256 timeout=m.return_value,
257 api_key="testkey",
258 )
259 self.assertEqual(ret, 10)
Aviv Keshetd1f04632014-05-09 11:33:46 -0700260
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400261
Don Garretta28be6d2016-06-16 18:09:35 -0700262class UploadSymbolsHelpersTest(cros_test_lib.TestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600263 """Test assorted helper functions and classes."""
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500264
Alex Klein1699fab2022-09-08 08:46:06 -0600265 def testIsTarball(self):
266 notTar = [
267 "/foo/bar/test.bin",
268 "/foo/bar/test.tar.bin",
269 "/foo/bar/test.faketar.gz",
270 "/foo/bar/test.nottgz",
271 ]
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500272
Alex Klein1699fab2022-09-08 08:46:06 -0600273 isTar = [
274 "/foo/bar/test.tar",
275 "/foo/bar/test.bin.tar",
276 "/foo/bar/test.bin.tar.bz2",
277 "/foo/bar/test.bin.tar.gz",
278 "/foo/bar/test.bin.tar.xz",
279 "/foo/bar/test.tbz2",
280 "/foo/bar/test.tbz",
281 "/foo/bar/test.tgz",
282 "/foo/bar/test.txz",
283 ]
Don Garretta28be6d2016-06-16 18:09:35 -0700284
Alex Klein1699fab2022-09-08 08:46:06 -0600285 for p in notTar:
286 self.assertFalse(upload_symbols.IsTarball(p))
Don Garretta28be6d2016-06-16 18:09:35 -0700287
Alex Klein1699fab2022-09-08 08:46:06 -0600288 for p in isTar:
289 self.assertTrue(upload_symbols.IsTarball(p))
Don Garretta28be6d2016-06-16 18:09:35 -0700290
Alex Klein1699fab2022-09-08 08:46:06 -0600291 def testBatchGenerator(self):
292 result = upload_symbols.BatchGenerator([], 2)
293 self.assertEqual(list(result), [])
Don Garretta28be6d2016-06-16 18:09:35 -0700294
Alex Klein1699fab2022-09-08 08:46:06 -0600295 # BatchGenerator accepts iterators, so passing it here is safe.
296 # pylint: disable=range-builtin-not-iterating
297 result = upload_symbols.BatchGenerator(range(6), 2)
298 self.assertEqual(list(result), [[0, 1], [2, 3], [4, 5]])
Don Garretta28be6d2016-06-16 18:09:35 -0700299
Alex Klein1699fab2022-09-08 08:46:06 -0600300 result = upload_symbols.BatchGenerator(range(7), 2)
301 self.assertEqual(list(result), [[0, 1], [2, 3], [4, 5], [6]])
302
Alex Kleind7197402023-04-05 13:05:29 -0600303 # Prove that we are streaming the results, not generating them all at
304 # once.
Alex Klein1699fab2022-09-08 08:46:06 -0600305 result = upload_symbols.BatchGenerator(itertools.repeat(0), 2)
306 self.assertEqual(next(result), [0, 0])
Don Garretta28be6d2016-06-16 18:09:35 -0700307
308
309class FindSymbolFilesTest(SymbolsTestBase):
Alex Klein1699fab2022-09-08 08:46:06 -0600310 """Test FindSymbolFiles."""
Don Garretta28be6d2016-06-16 18:09:35 -0700311
Alex Klein1699fab2022-09-08 08:46:06 -0600312 def setUp(self):
313 self.symfile = self.createSymbolFile("root.sym").file_name
314 self.innerfile = self.createSymbolFile(
315 os.path.join("nested", "inner.sym")
316 ).file_name
Don Garretta28be6d2016-06-16 18:09:35 -0700317
Alex Kleind7197402023-04-05 13:05:29 -0600318 # CreateTarball is having issues outside the chroot from open file
319 # tests.
Alex Klein1699fab2022-09-08 08:46:06 -0600320 #
321 # self.tarball = os.path.join(self.tempdir, 'syms.tar.gz')
322 # cros_build_lib.CreateTarball(
323 # 'syms.tar.gz', self.tempdir, inputs=(self.data))
Don Garretta28be6d2016-06-16 18:09:35 -0700324
Alex Klein1699fab2022-09-08 08:46:06 -0600325 def testEmpty(self):
326 symbols = list(upload_symbols.FindSymbolFiles(self.working, []))
327 self.assertEqual(symbols, [])
Don Garretta28be6d2016-06-16 18:09:35 -0700328
Alex Klein1699fab2022-09-08 08:46:06 -0600329 def testFile(self):
330 symbols = list(
331 upload_symbols.FindSymbolFiles(self.working, [self.symfile])
332 )
Don Garretta28be6d2016-06-16 18:09:35 -0700333
Alex Klein1699fab2022-09-08 08:46:06 -0600334 self.assertEqual(len(symbols), 1)
335 sf = symbols[0]
Don Garretta28be6d2016-06-16 18:09:35 -0700336
Alex Klein1699fab2022-09-08 08:46:06 -0600337 self.assertEqual(sf.display_name, "root.sym")
338 self.assertEqual(sf.display_path, self.symfile)
339 self.assertEqual(sf.file_name, self.symfile)
340 self.assertEqual(sf.status, upload_symbols.SymbolFile.INITIAL)
341 self.assertEqual(sf.FileSize(), len(self.FAT_CONTENT))
Don Garretta28be6d2016-06-16 18:09:35 -0700342
Alex Klein1699fab2022-09-08 08:46:06 -0600343 def testDir(self):
344 symbols = list(
345 upload_symbols.FindSymbolFiles(self.working, [self.data])
346 )
Don Garretta28be6d2016-06-16 18:09:35 -0700347
Alex Klein1699fab2022-09-08 08:46:06 -0600348 self.assertEqual(len(symbols), 2)
349 root = symbols[0]
350 nested = symbols[1]
Don Garretta28be6d2016-06-16 18:09:35 -0700351
Alex Klein1699fab2022-09-08 08:46:06 -0600352 self.assertEqual(root.display_name, "root.sym")
353 self.assertEqual(root.display_path, "root.sym")
354 self.assertEqual(root.file_name, self.symfile)
355 self.assertEqual(root.status, upload_symbols.SymbolFile.INITIAL)
356 self.assertEqual(root.FileSize(), len(self.FAT_CONTENT))
357
358 self.assertEqual(nested.display_name, "inner.sym")
359 self.assertEqual(nested.display_path, "nested/inner.sym")
360 self.assertEqual(nested.file_name, self.innerfile)
361 self.assertEqual(nested.status, upload_symbols.SymbolFile.INITIAL)
362 self.assertEqual(nested.FileSize(), len(self.FAT_CONTENT))
Don Garretta28be6d2016-06-16 18:09:35 -0700363
364
365class AdjustSymbolFileSizeTest(SymbolsTestBase):
Alex Klein1699fab2022-09-08 08:46:06 -0600366 """Test AdjustSymbolFileSize."""
Don Garretta28be6d2016-06-16 18:09:35 -0700367
Alex Klein1699fab2022-09-08 08:46:06 -0600368 def setUp(self):
369 self.slim = self.createSymbolFile("slim.sym", self.SLIM_CONTENT)
370 self.fat = self.createSymbolFile("fat.sym", self.FAT_CONTENT)
Don Garretta28be6d2016-06-16 18:09:35 -0700371
Alex Klein1699fab2022-09-08 08:46:06 -0600372 self.warn_mock = self.PatchObject(
373 cbuildbot_alerts, "PrintBuildbotStepWarnings"
374 )
Don Garretta28be6d2016-06-16 18:09:35 -0700375
Alex Klein1699fab2022-09-08 08:46:06 -0600376 def _testNotStripped(self, symbol, size=None, content=None):
377 start_file = symbol.file_name
378 after = upload_symbols.AdjustSymbolFileSize(symbol, self.working, size)
379 self.assertIs(after, symbol)
380 self.assertEqual(after.file_name, start_file)
381 if content is not None:
382 self.assertEqual(osutils.ReadFile(after.file_name), content)
Don Garretta28be6d2016-06-16 18:09:35 -0700383
Alex Klein1699fab2022-09-08 08:46:06 -0600384 def _testStripped(self, symbol, size=None, content=None):
385 after = upload_symbols.AdjustSymbolFileSize(symbol, self.working, size)
386 self.assertIs(after, symbol)
387 self.assertTrue(after.file_name.startswith(self.working))
388 if content is not None:
389 self.assertEqual(osutils.ReadFile(after.file_name), content)
Don Garretta28be6d2016-06-16 18:09:35 -0700390
Alex Klein1699fab2022-09-08 08:46:06 -0600391 def testSmall(self):
392 """Ensure that files smaller than the limit are not modified."""
393 self._testNotStripped(self.slim, 1024, self.SLIM_CONTENT)
394 self._testNotStripped(self.fat, 1024, self.FAT_CONTENT)
Don Garretta28be6d2016-06-16 18:09:35 -0700395
Alex Klein1699fab2022-09-08 08:46:06 -0600396 def testLarge(self):
397 """Ensure that files larger than the limit are modified."""
398 self._testStripped(self.slim, 1, self.SLIM_CONTENT)
399 self._testStripped(self.fat, 1, self.SLIM_CONTENT)
Don Garretta28be6d2016-06-16 18:09:35 -0700400
Alex Klein1699fab2022-09-08 08:46:06 -0600401 def testMixed(self):
402 """Test mix of large and small."""
403 strip_size = len(self.SLIM_CONTENT) + 1
Don Garretta28be6d2016-06-16 18:09:35 -0700404
Alex Klein1699fab2022-09-08 08:46:06 -0600405 self._testNotStripped(self.slim, strip_size, self.SLIM_CONTENT)
406 self._testStripped(self.fat, strip_size, self.SLIM_CONTENT)
Don Garretta28be6d2016-06-16 18:09:35 -0700407
Alex Klein1699fab2022-09-08 08:46:06 -0600408 def testSizeWarnings(self):
409 large = self.createSymbolFile(
410 "large.sym",
411 content=self.SLIM_CONTENT,
412 size=upload_symbols.CRASH_SERVER_FILE_LIMIT * 2,
413 )
Don Garretta28be6d2016-06-16 18:09:35 -0700414
Alex Klein1699fab2022-09-08 08:46:06 -0600415 # Would like to Strip as part of this test, but that really copies all
416 # of the sparse file content, which is too expensive for a unittest.
417 self._testNotStripped(large, None, None)
418
419 self.assertEqual(self.warn_mock.call_count, 1)
Don Garretta28be6d2016-06-16 18:09:35 -0700420
421
Mike Nichols137e82d2019-05-15 18:40:34 -0600422class DeduplicateTest(SymbolsTestBase):
Alex Klein1699fab2022-09-08 08:46:06 -0600423 """Test server Deduplication."""
Mike Nichols137e82d2019-05-15 18:40:34 -0600424
Alex Klein1699fab2022-09-08 08:46:06 -0600425 def setUp(self):
426 self.PatchObject(
427 upload_symbols,
428 "ExecRequest",
429 return_value={
430 "pairs": [
431 {
432 "status": "FOUND",
433 "symbolId": {
434 "debugFile": "sym1_sym",
435 "debugId": "BEAA9BE",
436 },
437 },
438 {
439 "status": "FOUND",
440 "symbolId": {
441 "debugFile": "sym2_sym",
442 "debugId": "B6B1A36",
443 },
444 },
445 {
446 "status": "MISSING",
447 "symbolId": {
448 "debugFile": "sym3_sym",
449 "debugId": "D4FC0FC",
450 },
451 },
452 ]
453 },
454 )
Mike Nichols137e82d2019-05-15 18:40:34 -0600455
Alex Klein1699fab2022-09-08 08:46:06 -0600456 def testFindDuplicates(self):
457 # The first two symbols will be duplicate, the third new.
458 sym1 = self.createSymbolFile("sym1.sym")
459 sym1.header = cros_generate_breakpad_symbols.SymbolHeader(
460 "cpu", "BEAA9BE", "sym1_sym", "os"
461 )
462 sym2 = self.createSymbolFile("sym2.sym")
463 sym2.header = cros_generate_breakpad_symbols.SymbolHeader(
464 "cpu", "B6B1A36", "sym2_sym", "os"
465 )
466 sym3 = self.createSymbolFile("sym3.sym")
467 sym3.header = cros_generate_breakpad_symbols.SymbolHeader(
468 "cpu", "D4FC0FC", "sym3_sym", "os"
469 )
Mike Nichols137e82d2019-05-15 18:40:34 -0600470
Alex Klein1699fab2022-09-08 08:46:06 -0600471 result = upload_symbols.FindDuplicates(
472 (sym1, sym2, sym3), "fake_url", api_key="testkey"
473 )
474 self.assertEqual(list(result), [sym1, sym2, sym3])
475
476 self.assertEqual(sym1.status, upload_symbols.SymbolFile.DUPLICATE)
477 self.assertEqual(sym2.status, upload_symbols.SymbolFile.DUPLICATE)
478 self.assertEqual(sym3.status, upload_symbols.SymbolFile.INITIAL)
Mike Nichols137e82d2019-05-15 18:40:34 -0600479
480
Don Garrettdeb2e032016-07-06 16:44:14 -0700481class PerformSymbolFilesUploadTest(SymbolsTestBase):
Alex Klein1699fab2022-09-08 08:46:06 -0600482 """Test PerformSymbolFile, and it's helper methods."""
Don Garretta28be6d2016-06-16 18:09:35 -0700483
Alex Klein1699fab2022-09-08 08:46:06 -0600484 def setUp(self):
485 self.sym_initial = self.createSymbolFile("initial.sym")
486 self.sym_error = self.createSymbolFile(
487 "error.sym", status=upload_symbols.SymbolFile.ERROR
488 )
489 self.sym_duplicate = self.createSymbolFile(
490 "duplicate.sym", status=upload_symbols.SymbolFile.DUPLICATE
491 )
492 self.sym_uploaded = self.createSymbolFile(
493 "uploaded.sym", status=upload_symbols.SymbolFile.UPLOADED
494 )
Don Garretta28be6d2016-06-16 18:09:35 -0700495
Alex Klein1699fab2022-09-08 08:46:06 -0600496 def testGetUploadTimeout(self):
497 """Test GetUploadTimeout helper function."""
498 # Timeout for small file.
499 self.assertEqual(
500 upload_symbols.GetUploadTimeout(self.sym_initial),
501 upload_symbols.UPLOAD_MIN_TIMEOUT,
502 )
Don Garretta28be6d2016-06-16 18:09:35 -0700503
Alex Klein1699fab2022-09-08 08:46:06 -0600504 # Timeout for 512M file.
505 large = self.createSymbolFile("large.sym", size=(512 * 1024 * 1024))
506 self.assertEqual(upload_symbols.GetUploadTimeout(large), 15 * 60)
Don Garretta28be6d2016-06-16 18:09:35 -0700507
Alex Klein1699fab2022-09-08 08:46:06 -0600508 def testUploadSymbolFile(self):
509 upload_symbols.UploadSymbolFile(
510 "fake_url", self.sym_initial, api_key="testkey"
511 )
512 # TODO: Examine mock in more detail to make sure request is correct.
513 self.assertEqual(self.request_mock.call_count, 3)
Don Garretta28be6d2016-06-16 18:09:35 -0700514
Alex Klein1699fab2022-09-08 08:46:06 -0600515 def testPerformSymbolsFileUpload(self):
516 """We upload on first try."""
517 symbols = [self.sym_initial]
Don Garrettdeb2e032016-07-06 16:44:14 -0700518
Alex Klein1699fab2022-09-08 08:46:06 -0600519 result = upload_symbols.PerformSymbolsFileUpload(
520 symbols, "fake_url", api_key="testkey"
521 )
Don Garretta28be6d2016-06-16 18:09:35 -0700522
Alex Klein1699fab2022-09-08 08:46:06 -0600523 self.assertEqual(list(result), symbols)
524 self.assertEqual(
525 self.sym_initial.status, upload_symbols.SymbolFile.UPLOADED
526 )
527 self.assertEqual(self.request_mock.call_count, 3)
Don Garretta28be6d2016-06-16 18:09:35 -0700528
Alex Klein1699fab2022-09-08 08:46:06 -0600529 def testPerformSymbolsFileUploadFailure(self):
530 """All network requests fail."""
531 self.request_mock.side_effect = IOError("network failure")
532 symbols = [self.sym_initial]
Don Garretta28be6d2016-06-16 18:09:35 -0700533
Alex Klein1699fab2022-09-08 08:46:06 -0600534 result = upload_symbols.PerformSymbolsFileUpload(
535 symbols, "fake_url", api_key="testkey"
536 )
Don Garretta28be6d2016-06-16 18:09:35 -0700537
Alex Klein1699fab2022-09-08 08:46:06 -0600538 self.assertEqual(list(result), symbols)
539 self.assertEqual(
540 self.sym_initial.status, upload_symbols.SymbolFile.ERROR
541 )
542 self.assertEqual(self.request_mock.call_count, 6)
Don Garretta28be6d2016-06-16 18:09:35 -0700543
Alex Klein1699fab2022-09-08 08:46:06 -0600544 def testPerformSymbolsFileUploadTransisentFailure(self):
545 """We fail once, then succeed."""
546 self.urlopen_mock.side_effect = (IOError("network failure"), None)
547 symbols = [self.sym_initial]
Don Garretta28be6d2016-06-16 18:09:35 -0700548
Alex Klein1699fab2022-09-08 08:46:06 -0600549 result = upload_symbols.PerformSymbolsFileUpload(
550 symbols, "fake_url", api_key="testkey"
551 )
Don Garrettdeb2e032016-07-06 16:44:14 -0700552
Alex Klein1699fab2022-09-08 08:46:06 -0600553 self.assertEqual(list(result), symbols)
554 self.assertEqual(
555 self.sym_initial.status, upload_symbols.SymbolFile.UPLOADED
556 )
557 self.assertEqual(self.request_mock.call_count, 3)
Don Garrettdeb2e032016-07-06 16:44:14 -0700558
Alex Klein1699fab2022-09-08 08:46:06 -0600559 def testPerformSymbolsFileUploadMixed(self):
560 """Upload symbols in mixed starting states.
Don Garrettdeb2e032016-07-06 16:44:14 -0700561
Alex Kleind7197402023-04-05 13:05:29 -0600562 Demonstrate that INITIAL and ERROR are uploaded, but DUPLICATE/UPLOADED
563 are ignored.
Alex Klein1699fab2022-09-08 08:46:06 -0600564 """
565 symbols = [
566 self.sym_initial,
567 self.sym_error,
568 self.sym_duplicate,
569 self.sym_uploaded,
570 ]
Don Garrettdeb2e032016-07-06 16:44:14 -0700571
Alex Klein1699fab2022-09-08 08:46:06 -0600572 result = upload_symbols.PerformSymbolsFileUpload(
573 symbols, "fake_url", api_key="testkey"
574 )
575
576 #
577 self.assertEqual(list(result), symbols)
578 self.assertEqual(
579 self.sym_initial.status, upload_symbols.SymbolFile.UPLOADED
580 )
581 self.assertEqual(
582 self.sym_error.status, upload_symbols.SymbolFile.UPLOADED
583 )
584 self.assertEqual(
585 self.sym_duplicate.status, upload_symbols.SymbolFile.DUPLICATE
586 )
587 self.assertEqual(
588 self.sym_uploaded.status, upload_symbols.SymbolFile.UPLOADED
589 )
590 self.assertEqual(self.request_mock.call_count, 6)
591
592 def testPerformSymbolsFileUploadErrorOut(self):
593 """Demonstate we exit only after X errors."""
594
595 symbol_count = upload_symbols.MAX_TOTAL_ERRORS_FOR_RETRY + 10
596 symbols = []
597 fail_file = None
598
599 # potentially twice as many errors as we should attempt.
600 for _ in range(symbol_count):
Alex Kleind7197402023-04-05 13:05:29 -0600601 # Each loop will get unique SymbolFile instances that use the same
602 # files.
Alex Klein1699fab2022-09-08 08:46:06 -0600603 fail = self.createSymbolFile("fail.sym")
604 fail_file = fail.file_name
605 symbols.append(self.createSymbolFile("pass.sym"))
606 symbols.append(fail)
607
608 # Mock out UploadSymbolFile and fail for fail.sym files.
609 def failSome(_url, symbol, _api_key):
610 if symbol.file_name == fail_file:
611 raise IOError("network failure")
612
613 upload_mock = self.PatchObject(
614 upload_symbols, "UploadSymbolFile", side_effect=failSome
615 )
616 upload_mock.__name__ = "UploadSymbolFileMock2"
617
618 result = upload_symbols.PerformSymbolsFileUpload(
619 symbols, "fake_url", api_key="testkey"
620 )
621
622 self.assertEqual(list(result), symbols)
623
624 passed = sum(
625 s.status == upload_symbols.SymbolFile.UPLOADED for s in symbols
626 )
627 failed = sum(
628 s.status == upload_symbols.SymbolFile.ERROR for s in symbols
629 )
630 skipped = sum(
631 s.status == upload_symbols.SymbolFile.INITIAL for s in symbols
632 )
633
634 # Shows we all pass.sym files worked until limit hit.
635 self.assertEqual(passed, upload_symbols.MAX_TOTAL_ERRORS_FOR_RETRY)
636
637 # Shows we all fail.sym files failed until limit hit.
638 self.assertEqual(failed, upload_symbols.MAX_TOTAL_ERRORS_FOR_RETRY)
639
640 # Shows both pass/fail were skipped after limit hit.
641 self.assertEqual(skipped, 10 * 2)
Don Garrettdeb2e032016-07-06 16:44:14 -0700642
643
Alex Klein1699fab2022-09-08 08:46:06 -0600644@pytest.mark.usefixtures("singleton_manager")
Don Garretta28be6d2016-06-16 18:09:35 -0700645class UploadSymbolsTest(SymbolsTestBase):
Alex Klein1699fab2022-09-08 08:46:06 -0600646 """Test UploadSymbols, along with most helper methods."""
Don Garretta28be6d2016-06-16 18:09:35 -0700647
Alex Klein1699fab2022-09-08 08:46:06 -0600648 def setUp(self):
649 # Results gathering.
650 self.failure_file = os.path.join(self.tempdir, "failures.txt")
Don Garretta28be6d2016-06-16 18:09:35 -0700651
Alex Klein1699fab2022-09-08 08:46:06 -0600652 def testUploadSymbolsEmpty(self):
653 """Upload dir is empty."""
654 result = upload_symbols.UploadSymbols([self.data], "fake_url")
Don Garretta28be6d2016-06-16 18:09:35 -0700655
Alex Klein1699fab2022-09-08 08:46:06 -0600656 self.assertEqual(result, 0)
657 self.assertEqual(self.urlopen_mock.call_count, 0)
Don Garretta28be6d2016-06-16 18:09:35 -0700658
Alex Klein1699fab2022-09-08 08:46:06 -0600659 def testUploadSymbols(self):
660 """Upload a few files."""
661 self.createSymbolFile("slim.sym", self.SLIM_CONTENT)
662 self.createSymbolFile(os.path.join("nested", "inner.sym"))
663 self.createSymbolFile("fat.sym", self.FAT_CONTENT)
Don Garretta28be6d2016-06-16 18:09:35 -0700664
Alex Klein1699fab2022-09-08 08:46:06 -0600665 result = upload_symbols.UploadSymbols(
666 [self.data],
667 "fake_url",
668 failed_list=self.failure_file,
669 strip_cfi=len(self.SLIM_CONTENT) + 1,
670 api_key="testkey",
671 )
Don Garretta28be6d2016-06-16 18:09:35 -0700672
Alex Klein1699fab2022-09-08 08:46:06 -0600673 self.assertEqual(result, 0)
674 self.assertEqual(self.request_mock.call_count, 10)
675 self.assertEqual(osutils.ReadFile(self.failure_file), "")
Don Garretta28be6d2016-06-16 18:09:35 -0700676
Alex Klein1699fab2022-09-08 08:46:06 -0600677 def testUploadSymbolsLimited(self):
678 """Upload a few files."""
679 self.createSymbolFile("slim.sym", self.SLIM_CONTENT)
680 self.createSymbolFile(os.path.join("nested", "inner.sym"))
681 self.createSymbolFile("fat.sym", self.FAT_CONTENT)
Don Garretta28be6d2016-06-16 18:09:35 -0700682
Alex Klein1699fab2022-09-08 08:46:06 -0600683 result = upload_symbols.UploadSymbols(
684 [self.data], "fake_url", upload_limit=2, api_key="testkey"
685 )
Don Garretta28be6d2016-06-16 18:09:35 -0700686
Alex Klein1699fab2022-09-08 08:46:06 -0600687 self.assertEqual(result, 0)
688 self.assertEqual(self.request_mock.call_count, 7)
689 self.assertNotExists(self.failure_file)
Don Garretta28be6d2016-06-16 18:09:35 -0700690
Alex Klein1699fab2022-09-08 08:46:06 -0600691 def testUploadSymbolsFailures(self):
692 """Upload a few files."""
693 self.createSymbolFile("pass.sym")
694 fail = self.createSymbolFile("fail.sym")
Don Garretta28be6d2016-06-16 18:09:35 -0700695
Alex Klein1699fab2022-09-08 08:46:06 -0600696 def failSome(_url, symbol, _api_key):
697 if symbol.file_name == fail.file_name:
698 raise IOError("network failure")
Don Garretta28be6d2016-06-16 18:09:35 -0700699
Alex Klein1699fab2022-09-08 08:46:06 -0600700 # Mock out UploadSymbolFile so it's easy to see which file to fail for.
701 upload_mock = self.PatchObject(
702 upload_symbols, "UploadSymbolFile", side_effect=failSome
703 )
704 # Mock __name__ for logging.
705 upload_mock.__name__ = "UploadSymbolFileMock"
Don Garretta28be6d2016-06-16 18:09:35 -0700706
Alex Klein1699fab2022-09-08 08:46:06 -0600707 result = upload_symbols.UploadSymbols(
708 [self.data],
709 "fake_url",
710 failed_list=self.failure_file,
711 api_key="testkey",
712 )
713
714 self.assertEqual(result, 1)
715 self.assertEqual(upload_mock.call_count, 7)
716 self.assertEqual(osutils.ReadFile(self.failure_file), "fail.sym\n")
717
Don Garretta28be6d2016-06-16 18:09:35 -0700718
Don Garretta28be6d2016-06-16 18:09:35 -0700719# TODO: We removed --network integration tests.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500720
721
Mike Frysingerea838d12014-12-08 11:55:32 -0500722def main(_argv):
Alex Klein1699fab2022-09-08 08:46:06 -0600723 # pylint: disable=protected-access
724 # Set timeouts small so that if the unit test hangs, it won't hang for long.
725 parallel._BackgroundTask.STARTUP_TIMEOUT = 5
726 parallel._BackgroundTask.EXIT_TIMEOUT = 5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400727
Alex Klein1699fab2022-09-08 08:46:06 -0600728 # Run the tests.
729 cros_test_lib.main(level="info", module=__name__)