blob: f0a0e17f56209fb316d27aa21387018b131b515d [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 Frysingerc1490af2023-05-04 10:26:17 -04005# TODO: We removed --network integration tests.
6
Mike Frysingereb753bf2013-11-22 16:05:35 -05007"""Unittests for upload_symbols.py"""
8
Mike Frysinger06da5202014-09-26 17:30:33 -05009import errno
Mike Frysingere852b072021-05-21 12:39:03 -040010import http.server
Don Garretta28be6d2016-06-16 18:09:35 -070011import itertools
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040012import os
Mike Frysinger0a2fd922014-09-12 20:23:42 -070013import signal
Mike Frysinger06da5202014-09-26 17:30:33 -050014import socket
Mike Frysingere852b072021-05-21 12:39:03 -040015import socketserver
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040016import sys
Aviv Keshetd1f04632014-05-09 11:33:46 -070017import time
Mike Frysinger166fea02021-02-12 05:30:33 -050018from unittest import mock
Mike Frysingere852b072021-05-21 12:39:03 -040019import urllib.request
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040020
Mike Frysinger40ffb532021-02-12 07:36:08 -050021import pytest # pylint: disable=import-error
Mike Frysinger40ffb532021-02-12 07:36:08 -050022
Mike Frysinger6db648e2018-07-24 19:57:58 -040023
Mike Frysinger079863c2014-10-09 23:16:46 -040024# We specifically set up a local server to connect to, so make sure we
25# delete any proxy settings that might screw that up. We also need to
26# do it here because modules that are imported below will implicitly
27# initialize with this proxy setting rather than dynamically pull it
28# on the fly :(.
Mike Frysinger92bdef52019-08-21 21:05:13 -040029# pylint: disable=wrong-import-position
Alex Klein1699fab2022-09-08 08:46:06 -060030os.environ.pop("http_proxy", None)
Mike Frysinger079863c2014-10-09 23:16:46 -040031
Aviv Keshetb7519e12016-10-04 00:50:00 -070032from chromite.lib import constants
Mike Frysingerbbd1f112016-09-08 18:25:11 -040033
Mike Frysinger40ffb532021-02-12 07:36:08 -050034
Mike Frysingerbbd1f112016-09-08 18:25:11 -040035# The isolateserver includes a bunch of third_party python packages that clash
36# with chromite's bundled third_party python packages (like oauth2client).
37# Since upload_symbols is not imported in to other parts of chromite, and there
38# are no deps in third_party we care about, purge the chromite copy. This way
39# we can use isolateserver for deduping.
40# TODO: If we ever sort out third_party/ handling and make it per-script opt-in,
41# we can purge this logic.
Mike Frysingera69df982023-03-21 16:52:27 -040042third_party = str(constants.CHROMITE_DIR / "third_party")
Mike Frysingerbbd1f112016-09-08 18:25:11 -040043while True:
Alex Klein1699fab2022-09-08 08:46:06 -060044 try:
45 sys.path.remove(third_party)
46 except ValueError:
47 break
Mike Frysingerbbd1f112016-09-08 18:25:11 -040048del third_party
49
Chris McDonaldb55b7032021-06-17 16:41:32 -060050from chromite.cbuildbot import cbuildbot_alerts
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040051from chromite.lib import cros_test_lib
52from chromite.lib import osutils
Mike Frysinger0a2fd922014-09-12 20:23:42 -070053from chromite.lib import remote_access
Mike Frysinger5e30a4b2014-02-12 20:23:04 -050054from chromite.scripts import cros_generate_breakpad_symbols
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040055from chromite.scripts import upload_symbols
56
Mike Frysinger0c0efa22014-02-09 23:32:23 -050057
Don Garretta28be6d2016-06-16 18:09:35 -070058class SymbolsTestBase(cros_test_lib.MockTempDirTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -060059 """Base class for most symbols tests."""
Don Garretta28be6d2016-06-16 18:09:35 -070060
Alex Klein1699fab2022-09-08 08:46:06 -060061 SLIM_CONTENT = """
Don Garretta28be6d2016-06-16 18:09:35 -070062some junk
63"""
64
Alex Klein1699fab2022-09-08 08:46:06 -060065 FAT_CONTENT = """
Don Garretta28be6d2016-06-16 18:09:35 -070066STACK CFI 1234
67some junk
68STACK CFI 1234
69"""
70
Alex Klein1699fab2022-09-08 08:46:06 -060071 def setUp(self):
72 # Make certain we don't use the network.
73 self.urlopen_mock = self.PatchObject(urllib.request, "urlopen")
74 self.request_mock = self.PatchObject(
75 upload_symbols,
76 "ExecRequest",
77 return_value={"uploadUrl": "testurl", "uploadKey": "asdgasgas"},
78 )
Don Garretta28be6d2016-06-16 18:09:35 -070079
Alex Klein1699fab2022-09-08 08:46:06 -060080 # Make 'uploads' go fast.
81 self.PatchObject(upload_symbols, "SLEEP_DELAY", 0)
82 self.PatchObject(upload_symbols, "INITIAL_RETRY_DELAY", 0)
Don Garretta28be6d2016-06-16 18:09:35 -070083
Alex Klein1699fab2022-09-08 08:46:06 -060084 # So our symbol file content doesn't have to be real.
85 self.PatchObject(
86 cros_generate_breakpad_symbols,
87 "ReadSymsHeader",
88 return_value=cros_generate_breakpad_symbols.SymbolHeader(
89 os="os", cpu="cpu", id="id", name="name"
90 ),
91 )
Don Garretta28be6d2016-06-16 18:09:35 -070092
Alex Klein1699fab2022-09-08 08:46:06 -060093 self.working = os.path.join(self.tempdir, "expand")
94 osutils.SafeMakedirs(self.working)
Don Garretta28be6d2016-06-16 18:09:35 -070095
Alex Klein1699fab2022-09-08 08:46:06 -060096 self.data = os.path.join(self.tempdir, "data")
97 osutils.SafeMakedirs(self.data)
Don Garretta28be6d2016-06-16 18:09:35 -070098
Alex Klein1699fab2022-09-08 08:46:06 -060099 def createSymbolFile(
100 self, filename, content=FAT_CONTENT, size=0, status=None, dedupe=False
101 ):
102 fullname = os.path.join(self.data, filename)
103 osutils.SafeMakedirs(os.path.dirname(fullname))
Don Garretta28be6d2016-06-16 18:09:35 -0700104
Alex Kleind7197402023-04-05 13:05:29 -0600105 # If a file size is given, force that to be the minimum file size.
106 # Create a sparse file so large files are practical.
Alex Klein1699fab2022-09-08 08:46:06 -0600107 with open(fullname, "w+b") as f:
108 f.truncate(size)
109 f.seek(0)
110 f.write(content.encode("utf-8"))
Don Garretta28be6d2016-06-16 18:09:35 -0700111
Alex Klein1699fab2022-09-08 08:46:06 -0600112 result = upload_symbols.SymbolFile(
113 display_path=filename, file_name=fullname
114 )
Don Garretta28be6d2016-06-16 18:09:35 -0700115
Alex Klein1699fab2022-09-08 08:46:06 -0600116 if status:
117 result.status = status
Don Garretta28be6d2016-06-16 18:09:35 -0700118
Alex Klein1699fab2022-09-08 08:46:06 -0600119 if dedupe:
120 result.dedupe_item = upload_symbols.DedupeItem(result)
121 result.dedupe_push_state = "push_state"
Don Garretta28be6d2016-06-16 18:09:35 -0700122
Alex Klein1699fab2022-09-08 08:46:06 -0600123 return result
Don Garretta28be6d2016-06-16 18:09:35 -0700124
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400125
Mike Frysingere852b072021-05-21 12:39:03 -0400126class SymbolServerRequestHandler(http.server.BaseHTTPRequestHandler):
Alex Klein1699fab2022-09-08 08:46:06 -0600127 """HTTP handler for symbol POSTs"""
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700128
Alex Klein1699fab2022-09-08 08:46:06 -0600129 RESP_CODE = None
130 RESP_MSG = None
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700131
Alex Klein1699fab2022-09-08 08:46:06 -0600132 def do_POST(self):
133 """Handle a POST request"""
Alex Kleind7197402023-04-05 13:05:29 -0600134 # Drain the data from the client. If we don't, we might write the
135 # response and close the socket before the client finishes, so they die
136 # with EPIPE.
Alex Klein1699fab2022-09-08 08:46:06 -0600137 clen = int(self.headers.get("Content-Length", "0"))
138 self.rfile.read(clen)
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700139
Alex Klein1699fab2022-09-08 08:46:06 -0600140 self.send_response(self.RESP_CODE, self.RESP_MSG)
141 self.end_headers()
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700142
Alex Klein1699fab2022-09-08 08:46:06 -0600143 # pylint: disable=arguments-differ
144 def log_message(self, *args, **kwargs):
145 """Stub the logger as it writes to stderr"""
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700146
147
Mike Frysingere852b072021-05-21 12:39:03 -0400148class SymbolServer(socketserver.ThreadingTCPServer, http.server.HTTPServer):
Alex Klein1699fab2022-09-08 08:46:06 -0600149 """Simple HTTP server that forks each request"""
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700150
151
Alex Klein1699fab2022-09-08 08:46:06 -0600152@pytest.mark.usefixtures("singleton_manager")
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700153class UploadSymbolsServerTest(cros_test_lib.MockTempDirTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600154 """Tests for UploadSymbols() and a local HTTP server"""
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700155
Alex Klein1699fab2022-09-08 08:46:06 -0600156 SYM_CONTENTS = """MODULE Linux arm 123-456 blkid
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700157PUBLIC 1471 0 main"""
158
Alex Klein1699fab2022-09-08 08:46:06 -0600159 def SpawnServer(self, RequestHandler):
160 """Spawn a new http server"""
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700161 while True:
Alex Klein1699fab2022-09-08 08:46:06 -0600162 try:
163 port = remote_access.GetUnusedPort()
164 address = ("", port)
165 self.httpd = SymbolServer(address, RequestHandler)
166 break
167 except socket.error as e:
168 if e.errno == errno.EADDRINUSE:
169 continue
170 raise
171 self.server_url = "http://localhost:%i/post/path" % port
172 self.httpd_pid = os.fork()
173 if self.httpd_pid == 0:
174 self.httpd.serve_forever(poll_interval=0.1)
175 sys.exit(0)
176 # The child runs the server, so close the socket in the parent.
177 self.httpd.server_close()
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700178
Alex Klein1699fab2022-09-08 08:46:06 -0600179 def setUp(self):
180 self.httpd_pid = None
181 self.httpd = None
182 self.server_url = None
183 self.sym_file = os.path.join(self.tempdir, "test.sym")
184 osutils.WriteFile(self.sym_file, self.SYM_CONTENTS)
185
186 # Stop sleeps and retries for these tests.
187 self.PatchObject(upload_symbols, "SLEEP_DELAY", 0)
188 self.PatchObject(upload_symbols, "INITIAL_RETRY_DELAY", 0)
189 self.PatchObject(upload_symbols, "MAX_RETRIES", 0)
190
191 def tearDown(self):
192 # Only kill the server if we forked one.
193 if self.httpd_pid:
194 os.kill(self.httpd_pid, signal.SIGUSR1)
195
196 def testSuccess(self):
197 """The server returns success for all uploads"""
198
199 class Handler(SymbolServerRequestHandler):
200 """Always return 200"""
201
202 RESP_CODE = 200
203 self.PatchObject(
204 upload_symbols,
205 "ExecRequest",
206 return_value={
207 "uploadUrl": "testurl",
208 "uploadKey": "testSuccess",
209 },
210 )
211
212 self.SpawnServer(Handler)
213 ret = upload_symbols.UploadSymbols(
214 sym_paths=[self.sym_file] * 10,
215 upload_url=self.server_url,
216 api_key="testSuccess",
217 )
218 self.assertEqual(ret, 0)
219
220 def testError(self):
221 """The server returns errors for all uploads"""
222
223 class Handler(SymbolServerRequestHandler):
224 """All connections error"""
225
226 RESP_CODE = 500
227 RESP_MSG = "Internal Server Error"
228
229 self.SpawnServer(Handler)
230 ret = upload_symbols.UploadSymbols(
231 sym_paths=[self.sym_file] * 10,
232 upload_url=self.server_url,
233 api_key="testkey",
234 )
235 self.assertEqual(ret, 10)
236
237 def testHungServer(self):
238 """The server chokes, but we recover"""
239
240 class Handler(SymbolServerRequestHandler):
241 """All connections choke forever"""
242
243 self.PatchObject(
244 upload_symbols, "ExecRequest", return_value={"pairs": []}
245 )
246
247 def do_POST(self):
248 while True:
249 time.sleep(1000)
250
251 self.SpawnServer(Handler)
252 with mock.patch.object(upload_symbols, "GetUploadTimeout") as m:
253 m.return_value = 0.01
254 ret = upload_symbols.UploadSymbols(
255 sym_paths=[self.sym_file] * 10,
256 upload_url=self.server_url,
257 timeout=m.return_value,
258 api_key="testkey",
259 )
260 self.assertEqual(ret, 10)
Aviv Keshetd1f04632014-05-09 11:33:46 -0700261
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400262
Don Garretta28be6d2016-06-16 18:09:35 -0700263class UploadSymbolsHelpersTest(cros_test_lib.TestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600264 """Test assorted helper functions and classes."""
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500265
Alex Klein1699fab2022-09-08 08:46:06 -0600266 def testIsTarball(self):
267 notTar = [
268 "/foo/bar/test.bin",
269 "/foo/bar/test.tar.bin",
270 "/foo/bar/test.faketar.gz",
271 "/foo/bar/test.nottgz",
272 ]
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500273
Alex Klein1699fab2022-09-08 08:46:06 -0600274 isTar = [
275 "/foo/bar/test.tar",
276 "/foo/bar/test.bin.tar",
277 "/foo/bar/test.bin.tar.bz2",
278 "/foo/bar/test.bin.tar.gz",
279 "/foo/bar/test.bin.tar.xz",
280 "/foo/bar/test.tbz2",
281 "/foo/bar/test.tbz",
282 "/foo/bar/test.tgz",
283 "/foo/bar/test.txz",
284 ]
Don Garretta28be6d2016-06-16 18:09:35 -0700285
Alex Klein1699fab2022-09-08 08:46:06 -0600286 for p in notTar:
287 self.assertFalse(upload_symbols.IsTarball(p))
Don Garretta28be6d2016-06-16 18:09:35 -0700288
Alex Klein1699fab2022-09-08 08:46:06 -0600289 for p in isTar:
290 self.assertTrue(upload_symbols.IsTarball(p))
Don Garretta28be6d2016-06-16 18:09:35 -0700291
Alex Klein1699fab2022-09-08 08:46:06 -0600292 def testBatchGenerator(self):
293 result = upload_symbols.BatchGenerator([], 2)
294 self.assertEqual(list(result), [])
Don Garretta28be6d2016-06-16 18:09:35 -0700295
Alex Klein1699fab2022-09-08 08:46:06 -0600296 # BatchGenerator accepts iterators, so passing it here is safe.
297 # pylint: disable=range-builtin-not-iterating
298 result = upload_symbols.BatchGenerator(range(6), 2)
299 self.assertEqual(list(result), [[0, 1], [2, 3], [4, 5]])
Don Garretta28be6d2016-06-16 18:09:35 -0700300
Alex Klein1699fab2022-09-08 08:46:06 -0600301 result = upload_symbols.BatchGenerator(range(7), 2)
302 self.assertEqual(list(result), [[0, 1], [2, 3], [4, 5], [6]])
303
Alex Kleind7197402023-04-05 13:05:29 -0600304 # Prove that we are streaming the results, not generating them all at
305 # once.
Alex Klein1699fab2022-09-08 08:46:06 -0600306 result = upload_symbols.BatchGenerator(itertools.repeat(0), 2)
307 self.assertEqual(next(result), [0, 0])
Don Garretta28be6d2016-06-16 18:09:35 -0700308
309
310class FindSymbolFilesTest(SymbolsTestBase):
Alex Klein1699fab2022-09-08 08:46:06 -0600311 """Test FindSymbolFiles."""
Don Garretta28be6d2016-06-16 18:09:35 -0700312
Alex Klein1699fab2022-09-08 08:46:06 -0600313 def setUp(self):
314 self.symfile = self.createSymbolFile("root.sym").file_name
315 self.innerfile = self.createSymbolFile(
316 os.path.join("nested", "inner.sym")
317 ).file_name
Don Garretta28be6d2016-06-16 18:09:35 -0700318
Alex Kleind7197402023-04-05 13:05:29 -0600319 # CreateTarball is having issues outside the chroot from open file
320 # tests.
Alex Klein1699fab2022-09-08 08:46:06 -0600321 #
322 # self.tarball = os.path.join(self.tempdir, 'syms.tar.gz')
323 # cros_build_lib.CreateTarball(
324 # 'syms.tar.gz', self.tempdir, inputs=(self.data))
Don Garretta28be6d2016-06-16 18:09:35 -0700325
Alex Klein1699fab2022-09-08 08:46:06 -0600326 def testEmpty(self):
327 symbols = list(upload_symbols.FindSymbolFiles(self.working, []))
328 self.assertEqual(symbols, [])
Don Garretta28be6d2016-06-16 18:09:35 -0700329
Alex Klein1699fab2022-09-08 08:46:06 -0600330 def testFile(self):
331 symbols = list(
332 upload_symbols.FindSymbolFiles(self.working, [self.symfile])
333 )
Don Garretta28be6d2016-06-16 18:09:35 -0700334
Alex Klein1699fab2022-09-08 08:46:06 -0600335 self.assertEqual(len(symbols), 1)
336 sf = symbols[0]
Don Garretta28be6d2016-06-16 18:09:35 -0700337
Alex Klein1699fab2022-09-08 08:46:06 -0600338 self.assertEqual(sf.display_name, "root.sym")
339 self.assertEqual(sf.display_path, self.symfile)
340 self.assertEqual(sf.file_name, self.symfile)
341 self.assertEqual(sf.status, upload_symbols.SymbolFile.INITIAL)
342 self.assertEqual(sf.FileSize(), len(self.FAT_CONTENT))
Don Garretta28be6d2016-06-16 18:09:35 -0700343
Alex Klein1699fab2022-09-08 08:46:06 -0600344 def testDir(self):
345 symbols = list(
346 upload_symbols.FindSymbolFiles(self.working, [self.data])
347 )
Don Garretta28be6d2016-06-16 18:09:35 -0700348
Alex Klein1699fab2022-09-08 08:46:06 -0600349 self.assertEqual(len(symbols), 2)
350 root = symbols[0]
351 nested = symbols[1]
Don Garretta28be6d2016-06-16 18:09:35 -0700352
Alex Klein1699fab2022-09-08 08:46:06 -0600353 self.assertEqual(root.display_name, "root.sym")
354 self.assertEqual(root.display_path, "root.sym")
355 self.assertEqual(root.file_name, self.symfile)
356 self.assertEqual(root.status, upload_symbols.SymbolFile.INITIAL)
357 self.assertEqual(root.FileSize(), len(self.FAT_CONTENT))
358
359 self.assertEqual(nested.display_name, "inner.sym")
360 self.assertEqual(nested.display_path, "nested/inner.sym")
361 self.assertEqual(nested.file_name, self.innerfile)
362 self.assertEqual(nested.status, upload_symbols.SymbolFile.INITIAL)
363 self.assertEqual(nested.FileSize(), len(self.FAT_CONTENT))
Don Garretta28be6d2016-06-16 18:09:35 -0700364
365
366class AdjustSymbolFileSizeTest(SymbolsTestBase):
Alex Klein1699fab2022-09-08 08:46:06 -0600367 """Test AdjustSymbolFileSize."""
Don Garretta28be6d2016-06-16 18:09:35 -0700368
Alex Klein1699fab2022-09-08 08:46:06 -0600369 def setUp(self):
370 self.slim = self.createSymbolFile("slim.sym", self.SLIM_CONTENT)
371 self.fat = self.createSymbolFile("fat.sym", self.FAT_CONTENT)
Don Garretta28be6d2016-06-16 18:09:35 -0700372
Alex Klein1699fab2022-09-08 08:46:06 -0600373 self.warn_mock = self.PatchObject(
374 cbuildbot_alerts, "PrintBuildbotStepWarnings"
375 )
Don Garretta28be6d2016-06-16 18:09:35 -0700376
Alex Klein1699fab2022-09-08 08:46:06 -0600377 def _testNotStripped(self, symbol, size=None, content=None):
378 start_file = symbol.file_name
379 after = upload_symbols.AdjustSymbolFileSize(symbol, self.working, size)
380 self.assertIs(after, symbol)
381 self.assertEqual(after.file_name, start_file)
382 if content is not None:
383 self.assertEqual(osutils.ReadFile(after.file_name), content)
Don Garretta28be6d2016-06-16 18:09:35 -0700384
Alex Klein1699fab2022-09-08 08:46:06 -0600385 def _testStripped(self, symbol, size=None, content=None):
386 after = upload_symbols.AdjustSymbolFileSize(symbol, self.working, size)
387 self.assertIs(after, symbol)
388 self.assertTrue(after.file_name.startswith(self.working))
389 if content is not None:
390 self.assertEqual(osutils.ReadFile(after.file_name), content)
Don Garretta28be6d2016-06-16 18:09:35 -0700391
Alex Klein1699fab2022-09-08 08:46:06 -0600392 def testSmall(self):
393 """Ensure that files smaller than the limit are not modified."""
394 self._testNotStripped(self.slim, 1024, self.SLIM_CONTENT)
395 self._testNotStripped(self.fat, 1024, self.FAT_CONTENT)
Don Garretta28be6d2016-06-16 18:09:35 -0700396
Alex Klein1699fab2022-09-08 08:46:06 -0600397 def testLarge(self):
398 """Ensure that files larger than the limit are modified."""
399 self._testStripped(self.slim, 1, self.SLIM_CONTENT)
400 self._testStripped(self.fat, 1, self.SLIM_CONTENT)
Don Garretta28be6d2016-06-16 18:09:35 -0700401
Alex Klein1699fab2022-09-08 08:46:06 -0600402 def testMixed(self):
403 """Test mix of large and small."""
404 strip_size = len(self.SLIM_CONTENT) + 1
Don Garretta28be6d2016-06-16 18:09:35 -0700405
Alex Klein1699fab2022-09-08 08:46:06 -0600406 self._testNotStripped(self.slim, strip_size, self.SLIM_CONTENT)
407 self._testStripped(self.fat, strip_size, self.SLIM_CONTENT)
Don Garretta28be6d2016-06-16 18:09:35 -0700408
Alex Klein1699fab2022-09-08 08:46:06 -0600409 def testSizeWarnings(self):
410 large = self.createSymbolFile(
411 "large.sym",
412 content=self.SLIM_CONTENT,
413 size=upload_symbols.CRASH_SERVER_FILE_LIMIT * 2,
414 )
Don Garretta28be6d2016-06-16 18:09:35 -0700415
Alex Klein1699fab2022-09-08 08:46:06 -0600416 # Would like to Strip as part of this test, but that really copies all
417 # of the sparse file content, which is too expensive for a unittest.
418 self._testNotStripped(large, None, None)
419
420 self.assertEqual(self.warn_mock.call_count, 1)
Don Garretta28be6d2016-06-16 18:09:35 -0700421
422
Mike Nichols137e82d2019-05-15 18:40:34 -0600423class DeduplicateTest(SymbolsTestBase):
Alex Klein1699fab2022-09-08 08:46:06 -0600424 """Test server Deduplication."""
Mike Nichols137e82d2019-05-15 18:40:34 -0600425
Alex Klein1699fab2022-09-08 08:46:06 -0600426 def setUp(self):
427 self.PatchObject(
428 upload_symbols,
429 "ExecRequest",
430 return_value={
431 "pairs": [
432 {
433 "status": "FOUND",
434 "symbolId": {
435 "debugFile": "sym1_sym",
436 "debugId": "BEAA9BE",
437 },
438 },
439 {
440 "status": "FOUND",
441 "symbolId": {
442 "debugFile": "sym2_sym",
443 "debugId": "B6B1A36",
444 },
445 },
446 {
447 "status": "MISSING",
448 "symbolId": {
449 "debugFile": "sym3_sym",
450 "debugId": "D4FC0FC",
451 },
452 },
453 ]
454 },
455 )
Mike Nichols137e82d2019-05-15 18:40:34 -0600456
Alex Klein1699fab2022-09-08 08:46:06 -0600457 def testFindDuplicates(self):
458 # The first two symbols will be duplicate, the third new.
459 sym1 = self.createSymbolFile("sym1.sym")
460 sym1.header = cros_generate_breakpad_symbols.SymbolHeader(
461 "cpu", "BEAA9BE", "sym1_sym", "os"
462 )
463 sym2 = self.createSymbolFile("sym2.sym")
464 sym2.header = cros_generate_breakpad_symbols.SymbolHeader(
465 "cpu", "B6B1A36", "sym2_sym", "os"
466 )
467 sym3 = self.createSymbolFile("sym3.sym")
468 sym3.header = cros_generate_breakpad_symbols.SymbolHeader(
469 "cpu", "D4FC0FC", "sym3_sym", "os"
470 )
Mike Nichols137e82d2019-05-15 18:40:34 -0600471
Alex Klein1699fab2022-09-08 08:46:06 -0600472 result = upload_symbols.FindDuplicates(
473 (sym1, sym2, sym3), "fake_url", api_key="testkey"
474 )
475 self.assertEqual(list(result), [sym1, sym2, sym3])
476
477 self.assertEqual(sym1.status, upload_symbols.SymbolFile.DUPLICATE)
478 self.assertEqual(sym2.status, upload_symbols.SymbolFile.DUPLICATE)
479 self.assertEqual(sym3.status, upload_symbols.SymbolFile.INITIAL)
Mike Nichols137e82d2019-05-15 18:40:34 -0600480
481
Don Garrettdeb2e032016-07-06 16:44:14 -0700482class PerformSymbolFilesUploadTest(SymbolsTestBase):
Alex Klein1699fab2022-09-08 08:46:06 -0600483 """Test PerformSymbolFile, and it's helper methods."""
Don Garretta28be6d2016-06-16 18:09:35 -0700484
Alex Klein1699fab2022-09-08 08:46:06 -0600485 def setUp(self):
486 self.sym_initial = self.createSymbolFile("initial.sym")
487 self.sym_error = self.createSymbolFile(
488 "error.sym", status=upload_symbols.SymbolFile.ERROR
489 )
490 self.sym_duplicate = self.createSymbolFile(
491 "duplicate.sym", status=upload_symbols.SymbolFile.DUPLICATE
492 )
493 self.sym_uploaded = self.createSymbolFile(
494 "uploaded.sym", status=upload_symbols.SymbolFile.UPLOADED
495 )
Don Garretta28be6d2016-06-16 18:09:35 -0700496
Alex Klein1699fab2022-09-08 08:46:06 -0600497 def testGetUploadTimeout(self):
498 """Test GetUploadTimeout helper function."""
499 # Timeout for small file.
500 self.assertEqual(
501 upload_symbols.GetUploadTimeout(self.sym_initial),
502 upload_symbols.UPLOAD_MIN_TIMEOUT,
503 )
Don Garretta28be6d2016-06-16 18:09:35 -0700504
Alex Klein1699fab2022-09-08 08:46:06 -0600505 # Timeout for 512M file.
506 large = self.createSymbolFile("large.sym", size=(512 * 1024 * 1024))
507 self.assertEqual(upload_symbols.GetUploadTimeout(large), 15 * 60)
Don Garretta28be6d2016-06-16 18:09:35 -0700508
Alex Klein1699fab2022-09-08 08:46:06 -0600509 def testUploadSymbolFile(self):
510 upload_symbols.UploadSymbolFile(
511 "fake_url", self.sym_initial, api_key="testkey"
512 )
513 # TODO: Examine mock in more detail to make sure request is correct.
514 self.assertEqual(self.request_mock.call_count, 3)
Don Garretta28be6d2016-06-16 18:09:35 -0700515
Alex Klein1699fab2022-09-08 08:46:06 -0600516 def testPerformSymbolsFileUpload(self):
517 """We upload on first try."""
518 symbols = [self.sym_initial]
Don Garrettdeb2e032016-07-06 16:44:14 -0700519
Alex Klein1699fab2022-09-08 08:46:06 -0600520 result = upload_symbols.PerformSymbolsFileUpload(
521 symbols, "fake_url", api_key="testkey"
522 )
Don Garretta28be6d2016-06-16 18:09:35 -0700523
Alex Klein1699fab2022-09-08 08:46:06 -0600524 self.assertEqual(list(result), symbols)
525 self.assertEqual(
526 self.sym_initial.status, upload_symbols.SymbolFile.UPLOADED
527 )
528 self.assertEqual(self.request_mock.call_count, 3)
Don Garretta28be6d2016-06-16 18:09:35 -0700529
Alex Klein1699fab2022-09-08 08:46:06 -0600530 def testPerformSymbolsFileUploadFailure(self):
531 """All network requests fail."""
532 self.request_mock.side_effect = IOError("network failure")
533 symbols = [self.sym_initial]
Don Garretta28be6d2016-06-16 18:09:35 -0700534
Alex Klein1699fab2022-09-08 08:46:06 -0600535 result = upload_symbols.PerformSymbolsFileUpload(
536 symbols, "fake_url", api_key="testkey"
537 )
Don Garretta28be6d2016-06-16 18:09:35 -0700538
Alex Klein1699fab2022-09-08 08:46:06 -0600539 self.assertEqual(list(result), symbols)
540 self.assertEqual(
541 self.sym_initial.status, upload_symbols.SymbolFile.ERROR
542 )
543 self.assertEqual(self.request_mock.call_count, 6)
Don Garretta28be6d2016-06-16 18:09:35 -0700544
Alex Klein1699fab2022-09-08 08:46:06 -0600545 def testPerformSymbolsFileUploadTransisentFailure(self):
546 """We fail once, then succeed."""
547 self.urlopen_mock.side_effect = (IOError("network failure"), None)
548 symbols = [self.sym_initial]
Don Garretta28be6d2016-06-16 18:09:35 -0700549
Alex Klein1699fab2022-09-08 08:46:06 -0600550 result = upload_symbols.PerformSymbolsFileUpload(
551 symbols, "fake_url", api_key="testkey"
552 )
Don Garrettdeb2e032016-07-06 16:44:14 -0700553
Alex Klein1699fab2022-09-08 08:46:06 -0600554 self.assertEqual(list(result), symbols)
555 self.assertEqual(
556 self.sym_initial.status, upload_symbols.SymbolFile.UPLOADED
557 )
558 self.assertEqual(self.request_mock.call_count, 3)
Don Garrettdeb2e032016-07-06 16:44:14 -0700559
Alex Klein1699fab2022-09-08 08:46:06 -0600560 def testPerformSymbolsFileUploadMixed(self):
561 """Upload symbols in mixed starting states.
Don Garrettdeb2e032016-07-06 16:44:14 -0700562
Alex Kleind7197402023-04-05 13:05:29 -0600563 Demonstrate that INITIAL and ERROR are uploaded, but DUPLICATE/UPLOADED
564 are ignored.
Alex Klein1699fab2022-09-08 08:46:06 -0600565 """
566 symbols = [
567 self.sym_initial,
568 self.sym_error,
569 self.sym_duplicate,
570 self.sym_uploaded,
571 ]
Don Garrettdeb2e032016-07-06 16:44:14 -0700572
Alex Klein1699fab2022-09-08 08:46:06 -0600573 result = upload_symbols.PerformSymbolsFileUpload(
574 symbols, "fake_url", api_key="testkey"
575 )
576
577 #
578 self.assertEqual(list(result), symbols)
579 self.assertEqual(
580 self.sym_initial.status, upload_symbols.SymbolFile.UPLOADED
581 )
582 self.assertEqual(
583 self.sym_error.status, upload_symbols.SymbolFile.UPLOADED
584 )
585 self.assertEqual(
586 self.sym_duplicate.status, upload_symbols.SymbolFile.DUPLICATE
587 )
588 self.assertEqual(
589 self.sym_uploaded.status, upload_symbols.SymbolFile.UPLOADED
590 )
591 self.assertEqual(self.request_mock.call_count, 6)
592
593 def testPerformSymbolsFileUploadErrorOut(self):
594 """Demonstate we exit only after X errors."""
595
596 symbol_count = upload_symbols.MAX_TOTAL_ERRORS_FOR_RETRY + 10
597 symbols = []
598 fail_file = None
599
600 # potentially twice as many errors as we should attempt.
601 for _ in range(symbol_count):
Alex Kleind7197402023-04-05 13:05:29 -0600602 # Each loop will get unique SymbolFile instances that use the same
603 # files.
Alex Klein1699fab2022-09-08 08:46:06 -0600604 fail = self.createSymbolFile("fail.sym")
605 fail_file = fail.file_name
606 symbols.append(self.createSymbolFile("pass.sym"))
607 symbols.append(fail)
608
609 # Mock out UploadSymbolFile and fail for fail.sym files.
610 def failSome(_url, symbol, _api_key):
611 if symbol.file_name == fail_file:
612 raise IOError("network failure")
613
614 upload_mock = self.PatchObject(
615 upload_symbols, "UploadSymbolFile", side_effect=failSome
616 )
617 upload_mock.__name__ = "UploadSymbolFileMock2"
618
619 result = upload_symbols.PerformSymbolsFileUpload(
620 symbols, "fake_url", api_key="testkey"
621 )
622
623 self.assertEqual(list(result), symbols)
624
625 passed = sum(
626 s.status == upload_symbols.SymbolFile.UPLOADED for s in symbols
627 )
628 failed = sum(
629 s.status == upload_symbols.SymbolFile.ERROR for s in symbols
630 )
631 skipped = sum(
632 s.status == upload_symbols.SymbolFile.INITIAL for s in symbols
633 )
634
635 # Shows we all pass.sym files worked until limit hit.
636 self.assertEqual(passed, upload_symbols.MAX_TOTAL_ERRORS_FOR_RETRY)
637
638 # Shows we all fail.sym files failed until limit hit.
639 self.assertEqual(failed, upload_symbols.MAX_TOTAL_ERRORS_FOR_RETRY)
640
641 # Shows both pass/fail were skipped after limit hit.
642 self.assertEqual(skipped, 10 * 2)
Don Garrettdeb2e032016-07-06 16:44:14 -0700643
644
Alex Klein1699fab2022-09-08 08:46:06 -0600645@pytest.mark.usefixtures("singleton_manager")
Don Garretta28be6d2016-06-16 18:09:35 -0700646class UploadSymbolsTest(SymbolsTestBase):
Alex Klein1699fab2022-09-08 08:46:06 -0600647 """Test UploadSymbols, along with most helper methods."""
Don Garretta28be6d2016-06-16 18:09:35 -0700648
Alex Klein1699fab2022-09-08 08:46:06 -0600649 def setUp(self):
650 # Results gathering.
651 self.failure_file = os.path.join(self.tempdir, "failures.txt")
Don Garretta28be6d2016-06-16 18:09:35 -0700652
Alex Klein1699fab2022-09-08 08:46:06 -0600653 def testUploadSymbolsEmpty(self):
654 """Upload dir is empty."""
655 result = upload_symbols.UploadSymbols([self.data], "fake_url")
Don Garretta28be6d2016-06-16 18:09:35 -0700656
Alex Klein1699fab2022-09-08 08:46:06 -0600657 self.assertEqual(result, 0)
658 self.assertEqual(self.urlopen_mock.call_count, 0)
Don Garretta28be6d2016-06-16 18:09:35 -0700659
Alex Klein1699fab2022-09-08 08:46:06 -0600660 def testUploadSymbols(self):
661 """Upload a few files."""
662 self.createSymbolFile("slim.sym", self.SLIM_CONTENT)
663 self.createSymbolFile(os.path.join("nested", "inner.sym"))
664 self.createSymbolFile("fat.sym", self.FAT_CONTENT)
Don Garretta28be6d2016-06-16 18:09:35 -0700665
Alex Klein1699fab2022-09-08 08:46:06 -0600666 result = upload_symbols.UploadSymbols(
667 [self.data],
668 "fake_url",
669 failed_list=self.failure_file,
670 strip_cfi=len(self.SLIM_CONTENT) + 1,
671 api_key="testkey",
672 )
Don Garretta28be6d2016-06-16 18:09:35 -0700673
Alex Klein1699fab2022-09-08 08:46:06 -0600674 self.assertEqual(result, 0)
675 self.assertEqual(self.request_mock.call_count, 10)
676 self.assertEqual(osutils.ReadFile(self.failure_file), "")
Don Garretta28be6d2016-06-16 18:09:35 -0700677
Alex Klein1699fab2022-09-08 08:46:06 -0600678 def testUploadSymbolsLimited(self):
679 """Upload a few files."""
680 self.createSymbolFile("slim.sym", self.SLIM_CONTENT)
681 self.createSymbolFile(os.path.join("nested", "inner.sym"))
682 self.createSymbolFile("fat.sym", self.FAT_CONTENT)
Don Garretta28be6d2016-06-16 18:09:35 -0700683
Alex Klein1699fab2022-09-08 08:46:06 -0600684 result = upload_symbols.UploadSymbols(
685 [self.data], "fake_url", upload_limit=2, api_key="testkey"
686 )
Don Garretta28be6d2016-06-16 18:09:35 -0700687
Alex Klein1699fab2022-09-08 08:46:06 -0600688 self.assertEqual(result, 0)
689 self.assertEqual(self.request_mock.call_count, 7)
690 self.assertNotExists(self.failure_file)
Don Garretta28be6d2016-06-16 18:09:35 -0700691
Alex Klein1699fab2022-09-08 08:46:06 -0600692 def testUploadSymbolsFailures(self):
693 """Upload a few files."""
694 self.createSymbolFile("pass.sym")
695 fail = self.createSymbolFile("fail.sym")
Don Garretta28be6d2016-06-16 18:09:35 -0700696
Alex Klein1699fab2022-09-08 08:46:06 -0600697 def failSome(_url, symbol, _api_key):
698 if symbol.file_name == fail.file_name:
699 raise IOError("network failure")
Don Garretta28be6d2016-06-16 18:09:35 -0700700
Alex Klein1699fab2022-09-08 08:46:06 -0600701 # Mock out UploadSymbolFile so it's easy to see which file to fail for.
702 upload_mock = self.PatchObject(
703 upload_symbols, "UploadSymbolFile", side_effect=failSome
704 )
705 # Mock __name__ for logging.
706 upload_mock.__name__ = "UploadSymbolFileMock"
Don Garretta28be6d2016-06-16 18:09:35 -0700707
Alex Klein1699fab2022-09-08 08:46:06 -0600708 result = upload_symbols.UploadSymbols(
709 [self.data],
710 "fake_url",
711 failed_list=self.failure_file,
712 api_key="testkey",
713 )
714
715 self.assertEqual(result, 1)
716 self.assertEqual(upload_mock.call_count, 7)
717 self.assertEqual(osutils.ReadFile(self.failure_file), "fail.sym\n")