blob: 955f644eac0b5f78cb2301030650342ea5714e7c [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 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
Alex Kleind7197402023-04-05 13:05:29 -0600302 # Prove that we are streaming the results, not generating them all at
303 # once.
Alex Klein1699fab2022-09-08 08:46:06 -0600304 result = upload_symbols.BatchGenerator(itertools.repeat(0), 2)
305 self.assertEqual(next(result), [0, 0])
Don Garretta28be6d2016-06-16 18:09:35 -0700306
307
308class FindSymbolFilesTest(SymbolsTestBase):
Alex Klein1699fab2022-09-08 08:46:06 -0600309 """Test FindSymbolFiles."""
Don Garretta28be6d2016-06-16 18:09:35 -0700310
Alex Klein1699fab2022-09-08 08:46:06 -0600311 def setUp(self):
312 self.symfile = self.createSymbolFile("root.sym").file_name
313 self.innerfile = self.createSymbolFile(
314 os.path.join("nested", "inner.sym")
315 ).file_name
Don Garretta28be6d2016-06-16 18:09:35 -0700316
Alex Kleind7197402023-04-05 13:05:29 -0600317 # CreateTarball is having issues outside the chroot from open file
318 # tests.
Alex Klein1699fab2022-09-08 08:46:06 -0600319 #
320 # self.tarball = os.path.join(self.tempdir, 'syms.tar.gz')
321 # cros_build_lib.CreateTarball(
322 # 'syms.tar.gz', self.tempdir, inputs=(self.data))
Don Garretta28be6d2016-06-16 18:09:35 -0700323
Alex Klein1699fab2022-09-08 08:46:06 -0600324 def testEmpty(self):
325 symbols = list(upload_symbols.FindSymbolFiles(self.working, []))
326 self.assertEqual(symbols, [])
Don Garretta28be6d2016-06-16 18:09:35 -0700327
Alex Klein1699fab2022-09-08 08:46:06 -0600328 def testFile(self):
329 symbols = list(
330 upload_symbols.FindSymbolFiles(self.working, [self.symfile])
331 )
Don Garretta28be6d2016-06-16 18:09:35 -0700332
Alex Klein1699fab2022-09-08 08:46:06 -0600333 self.assertEqual(len(symbols), 1)
334 sf = symbols[0]
Don Garretta28be6d2016-06-16 18:09:35 -0700335
Alex Klein1699fab2022-09-08 08:46:06 -0600336 self.assertEqual(sf.display_name, "root.sym")
337 self.assertEqual(sf.display_path, self.symfile)
338 self.assertEqual(sf.file_name, self.symfile)
339 self.assertEqual(sf.status, upload_symbols.SymbolFile.INITIAL)
340 self.assertEqual(sf.FileSize(), len(self.FAT_CONTENT))
Don Garretta28be6d2016-06-16 18:09:35 -0700341
Alex Klein1699fab2022-09-08 08:46:06 -0600342 def testDir(self):
343 symbols = list(
344 upload_symbols.FindSymbolFiles(self.working, [self.data])
345 )
Don Garretta28be6d2016-06-16 18:09:35 -0700346
Alex Klein1699fab2022-09-08 08:46:06 -0600347 self.assertEqual(len(symbols), 2)
348 root = symbols[0]
349 nested = symbols[1]
Don Garretta28be6d2016-06-16 18:09:35 -0700350
Alex Klein1699fab2022-09-08 08:46:06 -0600351 self.assertEqual(root.display_name, "root.sym")
352 self.assertEqual(root.display_path, "root.sym")
353 self.assertEqual(root.file_name, self.symfile)
354 self.assertEqual(root.status, upload_symbols.SymbolFile.INITIAL)
355 self.assertEqual(root.FileSize(), len(self.FAT_CONTENT))
356
357 self.assertEqual(nested.display_name, "inner.sym")
358 self.assertEqual(nested.display_path, "nested/inner.sym")
359 self.assertEqual(nested.file_name, self.innerfile)
360 self.assertEqual(nested.status, upload_symbols.SymbolFile.INITIAL)
361 self.assertEqual(nested.FileSize(), len(self.FAT_CONTENT))
Don Garretta28be6d2016-06-16 18:09:35 -0700362
363
364class AdjustSymbolFileSizeTest(SymbolsTestBase):
Alex Klein1699fab2022-09-08 08:46:06 -0600365 """Test AdjustSymbolFileSize."""
Don Garretta28be6d2016-06-16 18:09:35 -0700366
Alex Klein1699fab2022-09-08 08:46:06 -0600367 def setUp(self):
368 self.slim = self.createSymbolFile("slim.sym", self.SLIM_CONTENT)
369 self.fat = self.createSymbolFile("fat.sym", self.FAT_CONTENT)
Don Garretta28be6d2016-06-16 18:09:35 -0700370
Alex Klein1699fab2022-09-08 08:46:06 -0600371 self.warn_mock = self.PatchObject(
372 cbuildbot_alerts, "PrintBuildbotStepWarnings"
373 )
Don Garretta28be6d2016-06-16 18:09:35 -0700374
Alex Klein1699fab2022-09-08 08:46:06 -0600375 def _testNotStripped(self, symbol, size=None, content=None):
376 start_file = symbol.file_name
377 after = upload_symbols.AdjustSymbolFileSize(symbol, self.working, size)
378 self.assertIs(after, symbol)
379 self.assertEqual(after.file_name, start_file)
380 if content is not None:
381 self.assertEqual(osutils.ReadFile(after.file_name), content)
Don Garretta28be6d2016-06-16 18:09:35 -0700382
Alex Klein1699fab2022-09-08 08:46:06 -0600383 def _testStripped(self, symbol, size=None, content=None):
384 after = upload_symbols.AdjustSymbolFileSize(symbol, self.working, size)
385 self.assertIs(after, symbol)
386 self.assertTrue(after.file_name.startswith(self.working))
387 if content is not None:
388 self.assertEqual(osutils.ReadFile(after.file_name), content)
Don Garretta28be6d2016-06-16 18:09:35 -0700389
Alex Klein1699fab2022-09-08 08:46:06 -0600390 def testSmall(self):
391 """Ensure that files smaller than the limit are not modified."""
392 self._testNotStripped(self.slim, 1024, self.SLIM_CONTENT)
393 self._testNotStripped(self.fat, 1024, self.FAT_CONTENT)
Don Garretta28be6d2016-06-16 18:09:35 -0700394
Alex Klein1699fab2022-09-08 08:46:06 -0600395 def testLarge(self):
396 """Ensure that files larger than the limit are modified."""
397 self._testStripped(self.slim, 1, self.SLIM_CONTENT)
398 self._testStripped(self.fat, 1, self.SLIM_CONTENT)
Don Garretta28be6d2016-06-16 18:09:35 -0700399
Alex Klein1699fab2022-09-08 08:46:06 -0600400 def testMixed(self):
401 """Test mix of large and small."""
402 strip_size = len(self.SLIM_CONTENT) + 1
Don Garretta28be6d2016-06-16 18:09:35 -0700403
Alex Klein1699fab2022-09-08 08:46:06 -0600404 self._testNotStripped(self.slim, strip_size, self.SLIM_CONTENT)
405 self._testStripped(self.fat, strip_size, self.SLIM_CONTENT)
Don Garretta28be6d2016-06-16 18:09:35 -0700406
Alex Klein1699fab2022-09-08 08:46:06 -0600407 def testSizeWarnings(self):
408 large = self.createSymbolFile(
409 "large.sym",
410 content=self.SLIM_CONTENT,
411 size=upload_symbols.CRASH_SERVER_FILE_LIMIT * 2,
412 )
Don Garretta28be6d2016-06-16 18:09:35 -0700413
Alex Klein1699fab2022-09-08 08:46:06 -0600414 # Would like to Strip as part of this test, but that really copies all
415 # of the sparse file content, which is too expensive for a unittest.
416 self._testNotStripped(large, None, None)
417
418 self.assertEqual(self.warn_mock.call_count, 1)
Don Garretta28be6d2016-06-16 18:09:35 -0700419
420
Mike Nichols137e82d2019-05-15 18:40:34 -0600421class DeduplicateTest(SymbolsTestBase):
Alex Klein1699fab2022-09-08 08:46:06 -0600422 """Test server Deduplication."""
Mike Nichols137e82d2019-05-15 18:40:34 -0600423
Alex Klein1699fab2022-09-08 08:46:06 -0600424 def setUp(self):
425 self.PatchObject(
426 upload_symbols,
427 "ExecRequest",
428 return_value={
429 "pairs": [
430 {
431 "status": "FOUND",
432 "symbolId": {
433 "debugFile": "sym1_sym",
434 "debugId": "BEAA9BE",
435 },
436 },
437 {
438 "status": "FOUND",
439 "symbolId": {
440 "debugFile": "sym2_sym",
441 "debugId": "B6B1A36",
442 },
443 },
444 {
445 "status": "MISSING",
446 "symbolId": {
447 "debugFile": "sym3_sym",
448 "debugId": "D4FC0FC",
449 },
450 },
451 ]
452 },
453 )
Mike Nichols137e82d2019-05-15 18:40:34 -0600454
Alex Klein1699fab2022-09-08 08:46:06 -0600455 def testFindDuplicates(self):
456 # The first two symbols will be duplicate, the third new.
457 sym1 = self.createSymbolFile("sym1.sym")
458 sym1.header = cros_generate_breakpad_symbols.SymbolHeader(
459 "cpu", "BEAA9BE", "sym1_sym", "os"
460 )
461 sym2 = self.createSymbolFile("sym2.sym")
462 sym2.header = cros_generate_breakpad_symbols.SymbolHeader(
463 "cpu", "B6B1A36", "sym2_sym", "os"
464 )
465 sym3 = self.createSymbolFile("sym3.sym")
466 sym3.header = cros_generate_breakpad_symbols.SymbolHeader(
467 "cpu", "D4FC0FC", "sym3_sym", "os"
468 )
Mike Nichols137e82d2019-05-15 18:40:34 -0600469
Alex Klein1699fab2022-09-08 08:46:06 -0600470 result = upload_symbols.FindDuplicates(
471 (sym1, sym2, sym3), "fake_url", api_key="testkey"
472 )
473 self.assertEqual(list(result), [sym1, sym2, sym3])
474
475 self.assertEqual(sym1.status, upload_symbols.SymbolFile.DUPLICATE)
476 self.assertEqual(sym2.status, upload_symbols.SymbolFile.DUPLICATE)
477 self.assertEqual(sym3.status, upload_symbols.SymbolFile.INITIAL)
Mike Nichols137e82d2019-05-15 18:40:34 -0600478
479
Don Garrettdeb2e032016-07-06 16:44:14 -0700480class PerformSymbolFilesUploadTest(SymbolsTestBase):
Alex Klein1699fab2022-09-08 08:46:06 -0600481 """Test PerformSymbolFile, and it's helper methods."""
Don Garretta28be6d2016-06-16 18:09:35 -0700482
Alex Klein1699fab2022-09-08 08:46:06 -0600483 def setUp(self):
484 self.sym_initial = self.createSymbolFile("initial.sym")
485 self.sym_error = self.createSymbolFile(
486 "error.sym", status=upload_symbols.SymbolFile.ERROR
487 )
488 self.sym_duplicate = self.createSymbolFile(
489 "duplicate.sym", status=upload_symbols.SymbolFile.DUPLICATE
490 )
491 self.sym_uploaded = self.createSymbolFile(
492 "uploaded.sym", status=upload_symbols.SymbolFile.UPLOADED
493 )
Don Garretta28be6d2016-06-16 18:09:35 -0700494
Alex Klein1699fab2022-09-08 08:46:06 -0600495 def testGetUploadTimeout(self):
496 """Test GetUploadTimeout helper function."""
497 # Timeout for small file.
498 self.assertEqual(
499 upload_symbols.GetUploadTimeout(self.sym_initial),
500 upload_symbols.UPLOAD_MIN_TIMEOUT,
501 )
Don Garretta28be6d2016-06-16 18:09:35 -0700502
Alex Klein1699fab2022-09-08 08:46:06 -0600503 # Timeout for 512M file.
504 large = self.createSymbolFile("large.sym", size=(512 * 1024 * 1024))
505 self.assertEqual(upload_symbols.GetUploadTimeout(large), 15 * 60)
Don Garretta28be6d2016-06-16 18:09:35 -0700506
Alex Klein1699fab2022-09-08 08:46:06 -0600507 def testUploadSymbolFile(self):
508 upload_symbols.UploadSymbolFile(
509 "fake_url", self.sym_initial, api_key="testkey"
510 )
511 # TODO: Examine mock in more detail to make sure request is correct.
512 self.assertEqual(self.request_mock.call_count, 3)
Don Garretta28be6d2016-06-16 18:09:35 -0700513
Alex Klein1699fab2022-09-08 08:46:06 -0600514 def testPerformSymbolsFileUpload(self):
515 """We upload on first try."""
516 symbols = [self.sym_initial]
Don Garrettdeb2e032016-07-06 16:44:14 -0700517
Alex Klein1699fab2022-09-08 08:46:06 -0600518 result = upload_symbols.PerformSymbolsFileUpload(
519 symbols, "fake_url", api_key="testkey"
520 )
Don Garretta28be6d2016-06-16 18:09:35 -0700521
Alex Klein1699fab2022-09-08 08:46:06 -0600522 self.assertEqual(list(result), symbols)
523 self.assertEqual(
524 self.sym_initial.status, upload_symbols.SymbolFile.UPLOADED
525 )
526 self.assertEqual(self.request_mock.call_count, 3)
Don Garretta28be6d2016-06-16 18:09:35 -0700527
Alex Klein1699fab2022-09-08 08:46:06 -0600528 def testPerformSymbolsFileUploadFailure(self):
529 """All network requests fail."""
530 self.request_mock.side_effect = IOError("network failure")
531 symbols = [self.sym_initial]
Don Garretta28be6d2016-06-16 18:09:35 -0700532
Alex Klein1699fab2022-09-08 08:46:06 -0600533 result = upload_symbols.PerformSymbolsFileUpload(
534 symbols, "fake_url", api_key="testkey"
535 )
Don Garretta28be6d2016-06-16 18:09:35 -0700536
Alex Klein1699fab2022-09-08 08:46:06 -0600537 self.assertEqual(list(result), symbols)
538 self.assertEqual(
539 self.sym_initial.status, upload_symbols.SymbolFile.ERROR
540 )
541 self.assertEqual(self.request_mock.call_count, 6)
Don Garretta28be6d2016-06-16 18:09:35 -0700542
Alex Klein1699fab2022-09-08 08:46:06 -0600543 def testPerformSymbolsFileUploadTransisentFailure(self):
544 """We fail once, then succeed."""
545 self.urlopen_mock.side_effect = (IOError("network failure"), None)
546 symbols = [self.sym_initial]
Don Garretta28be6d2016-06-16 18:09:35 -0700547
Alex Klein1699fab2022-09-08 08:46:06 -0600548 result = upload_symbols.PerformSymbolsFileUpload(
549 symbols, "fake_url", api_key="testkey"
550 )
Don Garrettdeb2e032016-07-06 16:44:14 -0700551
Alex Klein1699fab2022-09-08 08:46:06 -0600552 self.assertEqual(list(result), symbols)
553 self.assertEqual(
554 self.sym_initial.status, upload_symbols.SymbolFile.UPLOADED
555 )
556 self.assertEqual(self.request_mock.call_count, 3)
Don Garrettdeb2e032016-07-06 16:44:14 -0700557
Alex Klein1699fab2022-09-08 08:46:06 -0600558 def testPerformSymbolsFileUploadMixed(self):
559 """Upload symbols in mixed starting states.
Don Garrettdeb2e032016-07-06 16:44:14 -0700560
Alex Kleind7197402023-04-05 13:05:29 -0600561 Demonstrate that INITIAL and ERROR are uploaded, but DUPLICATE/UPLOADED
562 are ignored.
Alex Klein1699fab2022-09-08 08:46:06 -0600563 """
564 symbols = [
565 self.sym_initial,
566 self.sym_error,
567 self.sym_duplicate,
568 self.sym_uploaded,
569 ]
Don Garrettdeb2e032016-07-06 16:44:14 -0700570
Alex Klein1699fab2022-09-08 08:46:06 -0600571 result = upload_symbols.PerformSymbolsFileUpload(
572 symbols, "fake_url", api_key="testkey"
573 )
574
575 #
576 self.assertEqual(list(result), symbols)
577 self.assertEqual(
578 self.sym_initial.status, upload_symbols.SymbolFile.UPLOADED
579 )
580 self.assertEqual(
581 self.sym_error.status, upload_symbols.SymbolFile.UPLOADED
582 )
583 self.assertEqual(
584 self.sym_duplicate.status, upload_symbols.SymbolFile.DUPLICATE
585 )
586 self.assertEqual(
587 self.sym_uploaded.status, upload_symbols.SymbolFile.UPLOADED
588 )
589 self.assertEqual(self.request_mock.call_count, 6)
590
591 def testPerformSymbolsFileUploadErrorOut(self):
592 """Demonstate we exit only after X errors."""
593
594 symbol_count = upload_symbols.MAX_TOTAL_ERRORS_FOR_RETRY + 10
595 symbols = []
596 fail_file = None
597
598 # potentially twice as many errors as we should attempt.
599 for _ in range(symbol_count):
Alex Kleind7197402023-04-05 13:05:29 -0600600 # Each loop will get unique SymbolFile instances that use the same
601 # files.
Alex Klein1699fab2022-09-08 08:46:06 -0600602 fail = self.createSymbolFile("fail.sym")
603 fail_file = fail.file_name
604 symbols.append(self.createSymbolFile("pass.sym"))
605 symbols.append(fail)
606
607 # Mock out UploadSymbolFile and fail for fail.sym files.
608 def failSome(_url, symbol, _api_key):
609 if symbol.file_name == fail_file:
610 raise IOError("network failure")
611
612 upload_mock = self.PatchObject(
613 upload_symbols, "UploadSymbolFile", side_effect=failSome
614 )
615 upload_mock.__name__ = "UploadSymbolFileMock2"
616
617 result = upload_symbols.PerformSymbolsFileUpload(
618 symbols, "fake_url", api_key="testkey"
619 )
620
621 self.assertEqual(list(result), symbols)
622
623 passed = sum(
624 s.status == upload_symbols.SymbolFile.UPLOADED for s in symbols
625 )
626 failed = sum(
627 s.status == upload_symbols.SymbolFile.ERROR for s in symbols
628 )
629 skipped = sum(
630 s.status == upload_symbols.SymbolFile.INITIAL for s in symbols
631 )
632
633 # Shows we all pass.sym files worked until limit hit.
634 self.assertEqual(passed, upload_symbols.MAX_TOTAL_ERRORS_FOR_RETRY)
635
636 # Shows we all fail.sym files failed until limit hit.
637 self.assertEqual(failed, upload_symbols.MAX_TOTAL_ERRORS_FOR_RETRY)
638
639 # Shows both pass/fail were skipped after limit hit.
640 self.assertEqual(skipped, 10 * 2)
Don Garrettdeb2e032016-07-06 16:44:14 -0700641
642
Alex Klein1699fab2022-09-08 08:46:06 -0600643@pytest.mark.usefixtures("singleton_manager")
Don Garretta28be6d2016-06-16 18:09:35 -0700644class UploadSymbolsTest(SymbolsTestBase):
Alex Klein1699fab2022-09-08 08:46:06 -0600645 """Test UploadSymbols, along with most helper methods."""
Don Garretta28be6d2016-06-16 18:09:35 -0700646
Alex Klein1699fab2022-09-08 08:46:06 -0600647 def setUp(self):
648 # Results gathering.
649 self.failure_file = os.path.join(self.tempdir, "failures.txt")
Don Garretta28be6d2016-06-16 18:09:35 -0700650
Alex Klein1699fab2022-09-08 08:46:06 -0600651 def testUploadSymbolsEmpty(self):
652 """Upload dir is empty."""
653 result = upload_symbols.UploadSymbols([self.data], "fake_url")
Don Garretta28be6d2016-06-16 18:09:35 -0700654
Alex Klein1699fab2022-09-08 08:46:06 -0600655 self.assertEqual(result, 0)
656 self.assertEqual(self.urlopen_mock.call_count, 0)
Don Garretta28be6d2016-06-16 18:09:35 -0700657
Alex Klein1699fab2022-09-08 08:46:06 -0600658 def testUploadSymbols(self):
659 """Upload a few files."""
660 self.createSymbolFile("slim.sym", self.SLIM_CONTENT)
661 self.createSymbolFile(os.path.join("nested", "inner.sym"))
662 self.createSymbolFile("fat.sym", self.FAT_CONTENT)
Don Garretta28be6d2016-06-16 18:09:35 -0700663
Alex Klein1699fab2022-09-08 08:46:06 -0600664 result = upload_symbols.UploadSymbols(
665 [self.data],
666 "fake_url",
667 failed_list=self.failure_file,
668 strip_cfi=len(self.SLIM_CONTENT) + 1,
669 api_key="testkey",
670 )
Don Garretta28be6d2016-06-16 18:09:35 -0700671
Alex Klein1699fab2022-09-08 08:46:06 -0600672 self.assertEqual(result, 0)
673 self.assertEqual(self.request_mock.call_count, 10)
674 self.assertEqual(osutils.ReadFile(self.failure_file), "")
Don Garretta28be6d2016-06-16 18:09:35 -0700675
Alex Klein1699fab2022-09-08 08:46:06 -0600676 def testUploadSymbolsLimited(self):
677 """Upload a few files."""
678 self.createSymbolFile("slim.sym", self.SLIM_CONTENT)
679 self.createSymbolFile(os.path.join("nested", "inner.sym"))
680 self.createSymbolFile("fat.sym", self.FAT_CONTENT)
Don Garretta28be6d2016-06-16 18:09:35 -0700681
Alex Klein1699fab2022-09-08 08:46:06 -0600682 result = upload_symbols.UploadSymbols(
683 [self.data], "fake_url", upload_limit=2, api_key="testkey"
684 )
Don Garretta28be6d2016-06-16 18:09:35 -0700685
Alex Klein1699fab2022-09-08 08:46:06 -0600686 self.assertEqual(result, 0)
687 self.assertEqual(self.request_mock.call_count, 7)
688 self.assertNotExists(self.failure_file)
Don Garretta28be6d2016-06-16 18:09:35 -0700689
Alex Klein1699fab2022-09-08 08:46:06 -0600690 def testUploadSymbolsFailures(self):
691 """Upload a few files."""
692 self.createSymbolFile("pass.sym")
693 fail = self.createSymbolFile("fail.sym")
Don Garretta28be6d2016-06-16 18:09:35 -0700694
Alex Klein1699fab2022-09-08 08:46:06 -0600695 def failSome(_url, symbol, _api_key):
696 if symbol.file_name == fail.file_name:
697 raise IOError("network failure")
Don Garretta28be6d2016-06-16 18:09:35 -0700698
Alex Klein1699fab2022-09-08 08:46:06 -0600699 # Mock out UploadSymbolFile so it's easy to see which file to fail for.
700 upload_mock = self.PatchObject(
701 upload_symbols, "UploadSymbolFile", side_effect=failSome
702 )
703 # Mock __name__ for logging.
704 upload_mock.__name__ = "UploadSymbolFileMock"
Don Garretta28be6d2016-06-16 18:09:35 -0700705
Alex Klein1699fab2022-09-08 08:46:06 -0600706 result = upload_symbols.UploadSymbols(
707 [self.data],
708 "fake_url",
709 failed_list=self.failure_file,
710 api_key="testkey",
711 )
712
713 self.assertEqual(result, 1)
714 self.assertEqual(upload_mock.call_count, 7)
715 self.assertEqual(osutils.ReadFile(self.failure_file), "fail.sym\n")