blob: 2fcc9cdf4157f26c0f18a8d1912d0116f9a12783 [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 Klein1699fab2022-09-08 08:46:06 -0600104 # If a file size is given, force that to be the minimum file size. Create
105 # a sparse file so large files are practical.
106 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"""
133 # Drain the data from the client. If we don't, we might write the response
134 # and close the socket before the client finishes, so they die with EPIPE.
135 clen = int(self.headers.get("Content-Length", "0"))
136 self.rfile.read(clen)
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700137
Alex Klein1699fab2022-09-08 08:46:06 -0600138 self.send_response(self.RESP_CODE, self.RESP_MSG)
139 self.end_headers()
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700140
Alex Klein1699fab2022-09-08 08:46:06 -0600141 # pylint: disable=arguments-differ
142 def log_message(self, *args, **kwargs):
143 """Stub the logger as it writes to stderr"""
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700144
145
Mike Frysingere852b072021-05-21 12:39:03 -0400146class SymbolServer(socketserver.ThreadingTCPServer, http.server.HTTPServer):
Alex Klein1699fab2022-09-08 08:46:06 -0600147 """Simple HTTP server that forks each request"""
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700148
149
Alex Klein1699fab2022-09-08 08:46:06 -0600150@pytest.mark.usefixtures("singleton_manager")
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700151class UploadSymbolsServerTest(cros_test_lib.MockTempDirTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600152 """Tests for UploadSymbols() and a local HTTP server"""
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700153
Alex Klein1699fab2022-09-08 08:46:06 -0600154 SYM_CONTENTS = """MODULE Linux arm 123-456 blkid
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700155PUBLIC 1471 0 main"""
156
Alex Klein1699fab2022-09-08 08:46:06 -0600157 def SpawnServer(self, RequestHandler):
158 """Spawn a new http server"""
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700159 while True:
Alex Klein1699fab2022-09-08 08:46:06 -0600160 try:
161 port = remote_access.GetUnusedPort()
162 address = ("", port)
163 self.httpd = SymbolServer(address, RequestHandler)
164 break
165 except socket.error as e:
166 if e.errno == errno.EADDRINUSE:
167 continue
168 raise
169 self.server_url = "http://localhost:%i/post/path" % port
170 self.httpd_pid = os.fork()
171 if self.httpd_pid == 0:
172 self.httpd.serve_forever(poll_interval=0.1)
173 sys.exit(0)
174 # The child runs the server, so close the socket in the parent.
175 self.httpd.server_close()
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700176
Alex Klein1699fab2022-09-08 08:46:06 -0600177 def setUp(self):
178 self.httpd_pid = None
179 self.httpd = None
180 self.server_url = None
181 self.sym_file = os.path.join(self.tempdir, "test.sym")
182 osutils.WriteFile(self.sym_file, self.SYM_CONTENTS)
183
184 # Stop sleeps and retries for these tests.
185 self.PatchObject(upload_symbols, "SLEEP_DELAY", 0)
186 self.PatchObject(upload_symbols, "INITIAL_RETRY_DELAY", 0)
187 self.PatchObject(upload_symbols, "MAX_RETRIES", 0)
188
189 def tearDown(self):
190 # Only kill the server if we forked one.
191 if self.httpd_pid:
192 os.kill(self.httpd_pid, signal.SIGUSR1)
193
194 def testSuccess(self):
195 """The server returns success for all uploads"""
196
197 class Handler(SymbolServerRequestHandler):
198 """Always return 200"""
199
200 RESP_CODE = 200
201 self.PatchObject(
202 upload_symbols,
203 "ExecRequest",
204 return_value={
205 "uploadUrl": "testurl",
206 "uploadKey": "testSuccess",
207 },
208 )
209
210 self.SpawnServer(Handler)
211 ret = upload_symbols.UploadSymbols(
212 sym_paths=[self.sym_file] * 10,
213 upload_url=self.server_url,
214 api_key="testSuccess",
215 )
216 self.assertEqual(ret, 0)
217
218 def testError(self):
219 """The server returns errors for all uploads"""
220
221 class Handler(SymbolServerRequestHandler):
222 """All connections error"""
223
224 RESP_CODE = 500
225 RESP_MSG = "Internal Server Error"
226
227 self.SpawnServer(Handler)
228 ret = upload_symbols.UploadSymbols(
229 sym_paths=[self.sym_file] * 10,
230 upload_url=self.server_url,
231 api_key="testkey",
232 )
233 self.assertEqual(ret, 10)
234
235 def testHungServer(self):
236 """The server chokes, but we recover"""
237
238 class Handler(SymbolServerRequestHandler):
239 """All connections choke forever"""
240
241 self.PatchObject(
242 upload_symbols, "ExecRequest", return_value={"pairs": []}
243 )
244
245 def do_POST(self):
246 while True:
247 time.sleep(1000)
248
249 self.SpawnServer(Handler)
250 with mock.patch.object(upload_symbols, "GetUploadTimeout") as m:
251 m.return_value = 0.01
252 ret = upload_symbols.UploadSymbols(
253 sym_paths=[self.sym_file] * 10,
254 upload_url=self.server_url,
255 timeout=m.return_value,
256 api_key="testkey",
257 )
258 self.assertEqual(ret, 10)
Aviv Keshetd1f04632014-05-09 11:33:46 -0700259
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400260
Don Garretta28be6d2016-06-16 18:09:35 -0700261class UploadSymbolsHelpersTest(cros_test_lib.TestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600262 """Test assorted helper functions and classes."""
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500263
Alex Klein1699fab2022-09-08 08:46:06 -0600264 def testIsTarball(self):
265 notTar = [
266 "/foo/bar/test.bin",
267 "/foo/bar/test.tar.bin",
268 "/foo/bar/test.faketar.gz",
269 "/foo/bar/test.nottgz",
270 ]
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500271
Alex Klein1699fab2022-09-08 08:46:06 -0600272 isTar = [
273 "/foo/bar/test.tar",
274 "/foo/bar/test.bin.tar",
275 "/foo/bar/test.bin.tar.bz2",
276 "/foo/bar/test.bin.tar.gz",
277 "/foo/bar/test.bin.tar.xz",
278 "/foo/bar/test.tbz2",
279 "/foo/bar/test.tbz",
280 "/foo/bar/test.tgz",
281 "/foo/bar/test.txz",
282 ]
Don Garretta28be6d2016-06-16 18:09:35 -0700283
Alex Klein1699fab2022-09-08 08:46:06 -0600284 for p in notTar:
285 self.assertFalse(upload_symbols.IsTarball(p))
Don Garretta28be6d2016-06-16 18:09:35 -0700286
Alex Klein1699fab2022-09-08 08:46:06 -0600287 for p in isTar:
288 self.assertTrue(upload_symbols.IsTarball(p))
Don Garretta28be6d2016-06-16 18:09:35 -0700289
Alex Klein1699fab2022-09-08 08:46:06 -0600290 def testBatchGenerator(self):
291 result = upload_symbols.BatchGenerator([], 2)
292 self.assertEqual(list(result), [])
Don Garretta28be6d2016-06-16 18:09:35 -0700293
Alex Klein1699fab2022-09-08 08:46:06 -0600294 # BatchGenerator accepts iterators, so passing it here is safe.
295 # pylint: disable=range-builtin-not-iterating
296 result = upload_symbols.BatchGenerator(range(6), 2)
297 self.assertEqual(list(result), [[0, 1], [2, 3], [4, 5]])
Don Garretta28be6d2016-06-16 18:09:35 -0700298
Alex Klein1699fab2022-09-08 08:46:06 -0600299 result = upload_symbols.BatchGenerator(range(7), 2)
300 self.assertEqual(list(result), [[0, 1], [2, 3], [4, 5], [6]])
301
302 # Prove that we are streaming the results, not generating them all at once.
303 result = upload_symbols.BatchGenerator(itertools.repeat(0), 2)
304 self.assertEqual(next(result), [0, 0])
Don Garretta28be6d2016-06-16 18:09:35 -0700305
306
307class FindSymbolFilesTest(SymbolsTestBase):
Alex Klein1699fab2022-09-08 08:46:06 -0600308 """Test FindSymbolFiles."""
Don Garretta28be6d2016-06-16 18:09:35 -0700309
Alex Klein1699fab2022-09-08 08:46:06 -0600310 def setUp(self):
311 self.symfile = self.createSymbolFile("root.sym").file_name
312 self.innerfile = self.createSymbolFile(
313 os.path.join("nested", "inner.sym")
314 ).file_name
Don Garretta28be6d2016-06-16 18:09:35 -0700315
Alex Klein1699fab2022-09-08 08:46:06 -0600316 # CreateTarball is having issues outside the chroot from open file tests.
317 #
318 # self.tarball = os.path.join(self.tempdir, 'syms.tar.gz')
319 # cros_build_lib.CreateTarball(
320 # 'syms.tar.gz', self.tempdir, inputs=(self.data))
Don Garretta28be6d2016-06-16 18:09:35 -0700321
Alex Klein1699fab2022-09-08 08:46:06 -0600322 def testEmpty(self):
323 symbols = list(upload_symbols.FindSymbolFiles(self.working, []))
324 self.assertEqual(symbols, [])
Don Garretta28be6d2016-06-16 18:09:35 -0700325
Alex Klein1699fab2022-09-08 08:46:06 -0600326 def testFile(self):
327 symbols = list(
328 upload_symbols.FindSymbolFiles(self.working, [self.symfile])
329 )
Don Garretta28be6d2016-06-16 18:09:35 -0700330
Alex Klein1699fab2022-09-08 08:46:06 -0600331 self.assertEqual(len(symbols), 1)
332 sf = symbols[0]
Don Garretta28be6d2016-06-16 18:09:35 -0700333
Alex Klein1699fab2022-09-08 08:46:06 -0600334 self.assertEqual(sf.display_name, "root.sym")
335 self.assertEqual(sf.display_path, self.symfile)
336 self.assertEqual(sf.file_name, self.symfile)
337 self.assertEqual(sf.status, upload_symbols.SymbolFile.INITIAL)
338 self.assertEqual(sf.FileSize(), len(self.FAT_CONTENT))
Don Garretta28be6d2016-06-16 18:09:35 -0700339
Alex Klein1699fab2022-09-08 08:46:06 -0600340 def testDir(self):
341 symbols = list(
342 upload_symbols.FindSymbolFiles(self.working, [self.data])
343 )
Don Garretta28be6d2016-06-16 18:09:35 -0700344
Alex Klein1699fab2022-09-08 08:46:06 -0600345 self.assertEqual(len(symbols), 2)
346 root = symbols[0]
347 nested = symbols[1]
Don Garretta28be6d2016-06-16 18:09:35 -0700348
Alex Klein1699fab2022-09-08 08:46:06 -0600349 self.assertEqual(root.display_name, "root.sym")
350 self.assertEqual(root.display_path, "root.sym")
351 self.assertEqual(root.file_name, self.symfile)
352 self.assertEqual(root.status, upload_symbols.SymbolFile.INITIAL)
353 self.assertEqual(root.FileSize(), len(self.FAT_CONTENT))
354
355 self.assertEqual(nested.display_name, "inner.sym")
356 self.assertEqual(nested.display_path, "nested/inner.sym")
357 self.assertEqual(nested.file_name, self.innerfile)
358 self.assertEqual(nested.status, upload_symbols.SymbolFile.INITIAL)
359 self.assertEqual(nested.FileSize(), len(self.FAT_CONTENT))
Don Garretta28be6d2016-06-16 18:09:35 -0700360
361
362class AdjustSymbolFileSizeTest(SymbolsTestBase):
Alex Klein1699fab2022-09-08 08:46:06 -0600363 """Test AdjustSymbolFileSize."""
Don Garretta28be6d2016-06-16 18:09:35 -0700364
Alex Klein1699fab2022-09-08 08:46:06 -0600365 def setUp(self):
366 self.slim = self.createSymbolFile("slim.sym", self.SLIM_CONTENT)
367 self.fat = self.createSymbolFile("fat.sym", self.FAT_CONTENT)
Don Garretta28be6d2016-06-16 18:09:35 -0700368
Alex Klein1699fab2022-09-08 08:46:06 -0600369 self.warn_mock = self.PatchObject(
370 cbuildbot_alerts, "PrintBuildbotStepWarnings"
371 )
Don Garretta28be6d2016-06-16 18:09:35 -0700372
Alex Klein1699fab2022-09-08 08:46:06 -0600373 def _testNotStripped(self, symbol, size=None, content=None):
374 start_file = symbol.file_name
375 after = upload_symbols.AdjustSymbolFileSize(symbol, self.working, size)
376 self.assertIs(after, symbol)
377 self.assertEqual(after.file_name, start_file)
378 if content is not None:
379 self.assertEqual(osutils.ReadFile(after.file_name), content)
Don Garretta28be6d2016-06-16 18:09:35 -0700380
Alex Klein1699fab2022-09-08 08:46:06 -0600381 def _testStripped(self, symbol, size=None, content=None):
382 after = upload_symbols.AdjustSymbolFileSize(symbol, self.working, size)
383 self.assertIs(after, symbol)
384 self.assertTrue(after.file_name.startswith(self.working))
385 if content is not None:
386 self.assertEqual(osutils.ReadFile(after.file_name), content)
Don Garretta28be6d2016-06-16 18:09:35 -0700387
Alex Klein1699fab2022-09-08 08:46:06 -0600388 def testSmall(self):
389 """Ensure that files smaller than the limit are not modified."""
390 self._testNotStripped(self.slim, 1024, self.SLIM_CONTENT)
391 self._testNotStripped(self.fat, 1024, self.FAT_CONTENT)
Don Garretta28be6d2016-06-16 18:09:35 -0700392
Alex Klein1699fab2022-09-08 08:46:06 -0600393 def testLarge(self):
394 """Ensure that files larger than the limit are modified."""
395 self._testStripped(self.slim, 1, self.SLIM_CONTENT)
396 self._testStripped(self.fat, 1, self.SLIM_CONTENT)
Don Garretta28be6d2016-06-16 18:09:35 -0700397
Alex Klein1699fab2022-09-08 08:46:06 -0600398 def testMixed(self):
399 """Test mix of large and small."""
400 strip_size = len(self.SLIM_CONTENT) + 1
Don Garretta28be6d2016-06-16 18:09:35 -0700401
Alex Klein1699fab2022-09-08 08:46:06 -0600402 self._testNotStripped(self.slim, strip_size, self.SLIM_CONTENT)
403 self._testStripped(self.fat, strip_size, self.SLIM_CONTENT)
Don Garretta28be6d2016-06-16 18:09:35 -0700404
Alex Klein1699fab2022-09-08 08:46:06 -0600405 def testSizeWarnings(self):
406 large = self.createSymbolFile(
407 "large.sym",
408 content=self.SLIM_CONTENT,
409 size=upload_symbols.CRASH_SERVER_FILE_LIMIT * 2,
410 )
Don Garretta28be6d2016-06-16 18:09:35 -0700411
Alex Klein1699fab2022-09-08 08:46:06 -0600412 # Would like to Strip as part of this test, but that really copies all
413 # of the sparse file content, which is too expensive for a unittest.
414 self._testNotStripped(large, None, None)
415
416 self.assertEqual(self.warn_mock.call_count, 1)
Don Garretta28be6d2016-06-16 18:09:35 -0700417
418
Mike Nichols137e82d2019-05-15 18:40:34 -0600419class DeduplicateTest(SymbolsTestBase):
Alex Klein1699fab2022-09-08 08:46:06 -0600420 """Test server Deduplication."""
Mike Nichols137e82d2019-05-15 18:40:34 -0600421
Alex Klein1699fab2022-09-08 08:46:06 -0600422 def setUp(self):
423 self.PatchObject(
424 upload_symbols,
425 "ExecRequest",
426 return_value={
427 "pairs": [
428 {
429 "status": "FOUND",
430 "symbolId": {
431 "debugFile": "sym1_sym",
432 "debugId": "BEAA9BE",
433 },
434 },
435 {
436 "status": "FOUND",
437 "symbolId": {
438 "debugFile": "sym2_sym",
439 "debugId": "B6B1A36",
440 },
441 },
442 {
443 "status": "MISSING",
444 "symbolId": {
445 "debugFile": "sym3_sym",
446 "debugId": "D4FC0FC",
447 },
448 },
449 ]
450 },
451 )
Mike Nichols137e82d2019-05-15 18:40:34 -0600452
Alex Klein1699fab2022-09-08 08:46:06 -0600453 def testFindDuplicates(self):
454 # The first two symbols will be duplicate, the third new.
455 sym1 = self.createSymbolFile("sym1.sym")
456 sym1.header = cros_generate_breakpad_symbols.SymbolHeader(
457 "cpu", "BEAA9BE", "sym1_sym", "os"
458 )
459 sym2 = self.createSymbolFile("sym2.sym")
460 sym2.header = cros_generate_breakpad_symbols.SymbolHeader(
461 "cpu", "B6B1A36", "sym2_sym", "os"
462 )
463 sym3 = self.createSymbolFile("sym3.sym")
464 sym3.header = cros_generate_breakpad_symbols.SymbolHeader(
465 "cpu", "D4FC0FC", "sym3_sym", "os"
466 )
Mike Nichols137e82d2019-05-15 18:40:34 -0600467
Alex Klein1699fab2022-09-08 08:46:06 -0600468 result = upload_symbols.FindDuplicates(
469 (sym1, sym2, sym3), "fake_url", api_key="testkey"
470 )
471 self.assertEqual(list(result), [sym1, sym2, sym3])
472
473 self.assertEqual(sym1.status, upload_symbols.SymbolFile.DUPLICATE)
474 self.assertEqual(sym2.status, upload_symbols.SymbolFile.DUPLICATE)
475 self.assertEqual(sym3.status, upload_symbols.SymbolFile.INITIAL)
Mike Nichols137e82d2019-05-15 18:40:34 -0600476
477
Don Garrettdeb2e032016-07-06 16:44:14 -0700478class PerformSymbolFilesUploadTest(SymbolsTestBase):
Alex Klein1699fab2022-09-08 08:46:06 -0600479 """Test PerformSymbolFile, and it's helper methods."""
Don Garretta28be6d2016-06-16 18:09:35 -0700480
Alex Klein1699fab2022-09-08 08:46:06 -0600481 def setUp(self):
482 self.sym_initial = self.createSymbolFile("initial.sym")
483 self.sym_error = self.createSymbolFile(
484 "error.sym", status=upload_symbols.SymbolFile.ERROR
485 )
486 self.sym_duplicate = self.createSymbolFile(
487 "duplicate.sym", status=upload_symbols.SymbolFile.DUPLICATE
488 )
489 self.sym_uploaded = self.createSymbolFile(
490 "uploaded.sym", status=upload_symbols.SymbolFile.UPLOADED
491 )
Don Garretta28be6d2016-06-16 18:09:35 -0700492
Alex Klein1699fab2022-09-08 08:46:06 -0600493 def testGetUploadTimeout(self):
494 """Test GetUploadTimeout helper function."""
495 # Timeout for small file.
496 self.assertEqual(
497 upload_symbols.GetUploadTimeout(self.sym_initial),
498 upload_symbols.UPLOAD_MIN_TIMEOUT,
499 )
Don Garretta28be6d2016-06-16 18:09:35 -0700500
Alex Klein1699fab2022-09-08 08:46:06 -0600501 # Timeout for 512M file.
502 large = self.createSymbolFile("large.sym", size=(512 * 1024 * 1024))
503 self.assertEqual(upload_symbols.GetUploadTimeout(large), 15 * 60)
Don Garretta28be6d2016-06-16 18:09:35 -0700504
Alex Klein1699fab2022-09-08 08:46:06 -0600505 def testUploadSymbolFile(self):
506 upload_symbols.UploadSymbolFile(
507 "fake_url", self.sym_initial, api_key="testkey"
508 )
509 # TODO: Examine mock in more detail to make sure request is correct.
510 self.assertEqual(self.request_mock.call_count, 3)
Don Garretta28be6d2016-06-16 18:09:35 -0700511
Alex Klein1699fab2022-09-08 08:46:06 -0600512 def testPerformSymbolsFileUpload(self):
513 """We upload on first try."""
514 symbols = [self.sym_initial]
Don Garrettdeb2e032016-07-06 16:44:14 -0700515
Alex Klein1699fab2022-09-08 08:46:06 -0600516 result = upload_symbols.PerformSymbolsFileUpload(
517 symbols, "fake_url", api_key="testkey"
518 )
Don Garretta28be6d2016-06-16 18:09:35 -0700519
Alex Klein1699fab2022-09-08 08:46:06 -0600520 self.assertEqual(list(result), symbols)
521 self.assertEqual(
522 self.sym_initial.status, upload_symbols.SymbolFile.UPLOADED
523 )
524 self.assertEqual(self.request_mock.call_count, 3)
Don Garretta28be6d2016-06-16 18:09:35 -0700525
Alex Klein1699fab2022-09-08 08:46:06 -0600526 def testPerformSymbolsFileUploadFailure(self):
527 """All network requests fail."""
528 self.request_mock.side_effect = IOError("network failure")
529 symbols = [self.sym_initial]
Don Garretta28be6d2016-06-16 18:09:35 -0700530
Alex Klein1699fab2022-09-08 08:46:06 -0600531 result = upload_symbols.PerformSymbolsFileUpload(
532 symbols, "fake_url", api_key="testkey"
533 )
Don Garretta28be6d2016-06-16 18:09:35 -0700534
Alex Klein1699fab2022-09-08 08:46:06 -0600535 self.assertEqual(list(result), symbols)
536 self.assertEqual(
537 self.sym_initial.status, upload_symbols.SymbolFile.ERROR
538 )
539 self.assertEqual(self.request_mock.call_count, 6)
Don Garretta28be6d2016-06-16 18:09:35 -0700540
Alex Klein1699fab2022-09-08 08:46:06 -0600541 def testPerformSymbolsFileUploadTransisentFailure(self):
542 """We fail once, then succeed."""
543 self.urlopen_mock.side_effect = (IOError("network failure"), None)
544 symbols = [self.sym_initial]
Don Garretta28be6d2016-06-16 18:09:35 -0700545
Alex Klein1699fab2022-09-08 08:46:06 -0600546 result = upload_symbols.PerformSymbolsFileUpload(
547 symbols, "fake_url", api_key="testkey"
548 )
Don Garrettdeb2e032016-07-06 16:44:14 -0700549
Alex Klein1699fab2022-09-08 08:46:06 -0600550 self.assertEqual(list(result), symbols)
551 self.assertEqual(
552 self.sym_initial.status, upload_symbols.SymbolFile.UPLOADED
553 )
554 self.assertEqual(self.request_mock.call_count, 3)
Don Garrettdeb2e032016-07-06 16:44:14 -0700555
Alex Klein1699fab2022-09-08 08:46:06 -0600556 def testPerformSymbolsFileUploadMixed(self):
557 """Upload symbols in mixed starting states.
Don Garrettdeb2e032016-07-06 16:44:14 -0700558
Alex Klein1699fab2022-09-08 08:46:06 -0600559 Demonstrate that INITIAL and ERROR are uploaded, but DUPLICATE/UPLOADED are
560 ignored.
561 """
562 symbols = [
563 self.sym_initial,
564 self.sym_error,
565 self.sym_duplicate,
566 self.sym_uploaded,
567 ]
Don Garrettdeb2e032016-07-06 16:44:14 -0700568
Alex Klein1699fab2022-09-08 08:46:06 -0600569 result = upload_symbols.PerformSymbolsFileUpload(
570 symbols, "fake_url", api_key="testkey"
571 )
572
573 #
574 self.assertEqual(list(result), symbols)
575 self.assertEqual(
576 self.sym_initial.status, upload_symbols.SymbolFile.UPLOADED
577 )
578 self.assertEqual(
579 self.sym_error.status, upload_symbols.SymbolFile.UPLOADED
580 )
581 self.assertEqual(
582 self.sym_duplicate.status, upload_symbols.SymbolFile.DUPLICATE
583 )
584 self.assertEqual(
585 self.sym_uploaded.status, upload_symbols.SymbolFile.UPLOADED
586 )
587 self.assertEqual(self.request_mock.call_count, 6)
588
589 def testPerformSymbolsFileUploadErrorOut(self):
590 """Demonstate we exit only after X errors."""
591
592 symbol_count = upload_symbols.MAX_TOTAL_ERRORS_FOR_RETRY + 10
593 symbols = []
594 fail_file = None
595
596 # potentially twice as many errors as we should attempt.
597 for _ in range(symbol_count):
598 # Each loop will get unique SymbolFile instances that use the same files.
599 fail = self.createSymbolFile("fail.sym")
600 fail_file = fail.file_name
601 symbols.append(self.createSymbolFile("pass.sym"))
602 symbols.append(fail)
603
604 # Mock out UploadSymbolFile and fail for fail.sym files.
605 def failSome(_url, symbol, _api_key):
606 if symbol.file_name == fail_file:
607 raise IOError("network failure")
608
609 upload_mock = self.PatchObject(
610 upload_symbols, "UploadSymbolFile", side_effect=failSome
611 )
612 upload_mock.__name__ = "UploadSymbolFileMock2"
613
614 result = upload_symbols.PerformSymbolsFileUpload(
615 symbols, "fake_url", api_key="testkey"
616 )
617
618 self.assertEqual(list(result), symbols)
619
620 passed = sum(
621 s.status == upload_symbols.SymbolFile.UPLOADED for s in symbols
622 )
623 failed = sum(
624 s.status == upload_symbols.SymbolFile.ERROR for s in symbols
625 )
626 skipped = sum(
627 s.status == upload_symbols.SymbolFile.INITIAL for s in symbols
628 )
629
630 # Shows we all pass.sym files worked until limit hit.
631 self.assertEqual(passed, upload_symbols.MAX_TOTAL_ERRORS_FOR_RETRY)
632
633 # Shows we all fail.sym files failed until limit hit.
634 self.assertEqual(failed, upload_symbols.MAX_TOTAL_ERRORS_FOR_RETRY)
635
636 # Shows both pass/fail were skipped after limit hit.
637 self.assertEqual(skipped, 10 * 2)
Don Garrettdeb2e032016-07-06 16:44:14 -0700638
639
Alex Klein1699fab2022-09-08 08:46:06 -0600640@pytest.mark.usefixtures("singleton_manager")
Don Garretta28be6d2016-06-16 18:09:35 -0700641class UploadSymbolsTest(SymbolsTestBase):
Alex Klein1699fab2022-09-08 08:46:06 -0600642 """Test UploadSymbols, along with most helper methods."""
Don Garretta28be6d2016-06-16 18:09:35 -0700643
Alex Klein1699fab2022-09-08 08:46:06 -0600644 def setUp(self):
645 # Results gathering.
646 self.failure_file = os.path.join(self.tempdir, "failures.txt")
Don Garretta28be6d2016-06-16 18:09:35 -0700647
Alex Klein1699fab2022-09-08 08:46:06 -0600648 def testUploadSymbolsEmpty(self):
649 """Upload dir is empty."""
650 result = upload_symbols.UploadSymbols([self.data], "fake_url")
Don Garretta28be6d2016-06-16 18:09:35 -0700651
Alex Klein1699fab2022-09-08 08:46:06 -0600652 self.assertEqual(result, 0)
653 self.assertEqual(self.urlopen_mock.call_count, 0)
Don Garretta28be6d2016-06-16 18:09:35 -0700654
Alex Klein1699fab2022-09-08 08:46:06 -0600655 def testUploadSymbols(self):
656 """Upload a few files."""
657 self.createSymbolFile("slim.sym", self.SLIM_CONTENT)
658 self.createSymbolFile(os.path.join("nested", "inner.sym"))
659 self.createSymbolFile("fat.sym", self.FAT_CONTENT)
Don Garretta28be6d2016-06-16 18:09:35 -0700660
Alex Klein1699fab2022-09-08 08:46:06 -0600661 result = upload_symbols.UploadSymbols(
662 [self.data],
663 "fake_url",
664 failed_list=self.failure_file,
665 strip_cfi=len(self.SLIM_CONTENT) + 1,
666 api_key="testkey",
667 )
Don Garretta28be6d2016-06-16 18:09:35 -0700668
Alex Klein1699fab2022-09-08 08:46:06 -0600669 self.assertEqual(result, 0)
670 self.assertEqual(self.request_mock.call_count, 10)
671 self.assertEqual(osutils.ReadFile(self.failure_file), "")
Don Garretta28be6d2016-06-16 18:09:35 -0700672
Alex Klein1699fab2022-09-08 08:46:06 -0600673 def testUploadSymbolsLimited(self):
674 """Upload a few files."""
675 self.createSymbolFile("slim.sym", self.SLIM_CONTENT)
676 self.createSymbolFile(os.path.join("nested", "inner.sym"))
677 self.createSymbolFile("fat.sym", self.FAT_CONTENT)
Don Garretta28be6d2016-06-16 18:09:35 -0700678
Alex Klein1699fab2022-09-08 08:46:06 -0600679 result = upload_symbols.UploadSymbols(
680 [self.data], "fake_url", upload_limit=2, api_key="testkey"
681 )
Don Garretta28be6d2016-06-16 18:09:35 -0700682
Alex Klein1699fab2022-09-08 08:46:06 -0600683 self.assertEqual(result, 0)
684 self.assertEqual(self.request_mock.call_count, 7)
685 self.assertNotExists(self.failure_file)
Don Garretta28be6d2016-06-16 18:09:35 -0700686
Alex Klein1699fab2022-09-08 08:46:06 -0600687 def testUploadSymbolsFailures(self):
688 """Upload a few files."""
689 self.createSymbolFile("pass.sym")
690 fail = self.createSymbolFile("fail.sym")
Don Garretta28be6d2016-06-16 18:09:35 -0700691
Alex Klein1699fab2022-09-08 08:46:06 -0600692 def failSome(_url, symbol, _api_key):
693 if symbol.file_name == fail.file_name:
694 raise IOError("network failure")
Don Garretta28be6d2016-06-16 18:09:35 -0700695
Alex Klein1699fab2022-09-08 08:46:06 -0600696 # Mock out UploadSymbolFile so it's easy to see which file to fail for.
697 upload_mock = self.PatchObject(
698 upload_symbols, "UploadSymbolFile", side_effect=failSome
699 )
700 # Mock __name__ for logging.
701 upload_mock.__name__ = "UploadSymbolFileMock"
Don Garretta28be6d2016-06-16 18:09:35 -0700702
Alex Klein1699fab2022-09-08 08:46:06 -0600703 result = upload_symbols.UploadSymbols(
704 [self.data],
705 "fake_url",
706 failed_list=self.failure_file,
707 api_key="testkey",
708 )
709
710 self.assertEqual(result, 1)
711 self.assertEqual(upload_mock.call_count, 7)
712 self.assertEqual(osutils.ReadFile(self.failure_file), "fail.sym\n")
713
Don Garretta28be6d2016-06-16 18:09:35 -0700714
Don Garretta28be6d2016-06-16 18:09:35 -0700715# TODO: We removed --network integration tests.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500716
717
Mike Frysingerea838d12014-12-08 11:55:32 -0500718def main(_argv):
Alex Klein1699fab2022-09-08 08:46:06 -0600719 # pylint: disable=protected-access
720 # Set timeouts small so that if the unit test hangs, it won't hang for long.
721 parallel._BackgroundTask.STARTUP_TIMEOUT = 5
722 parallel._BackgroundTask.EXIT_TIMEOUT = 5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400723
Alex Klein1699fab2022-09-08 08:46:06 -0600724 # Run the tests.
725 cros_test_lib.main(level="info", module=__name__)