blob: 043bc706a93dd57d8ba7f96e3814bde8411e929d [file] [log] [blame]
Mike Frysingerf1ba7ad2022-09-12 05:42:57 -04001# Copyright 2013 The ChromiumOS Authors
Mike Frysinger69cb41d2013-08-11 20:08:19 -04002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Don Garrett25f309a2014-03-19 14:02:12 -07005"""Test cros_generate_breakpad_symbols."""
6
Mike Frysinger69cb41d2013-08-11 20:08:19 -04007import ctypes
Mike Frysinger374ba4f2019-11-14 23:45:15 -05008import io
Ian Barkley-Yeungc5b6f582023-03-08 18:23:07 -08009import logging
10import multiprocessing
Mike Frysinger69cb41d2013-08-11 20:08:19 -040011import os
Ian Barkley-Yeungc5b6f582023-03-08 18:23:07 -080012import pathlib
Mike Frysinger166fea02021-02-12 05:30:33 -050013from unittest import mock
Mike Frysinger69cb41d2013-08-11 20:08:19 -040014
Mike Frysinger69cb41d2013-08-11 20:08:19 -040015from chromite.lib import cros_test_lib
16from chromite.lib import osutils
Mike Frysinger69cb41d2013-08-11 20:08:19 -040017from chromite.lib import parallel_unittest
18from chromite.lib import partial_mock
19from chromite.scripts import cros_generate_breakpad_symbols
20
Mike Frysinger69cb41d2013-08-11 20:08:19 -040021
22class FindDebugDirMock(partial_mock.PartialMock):
Alex Klein1699fab2022-09-08 08:46:06 -060023 """Mock out the DebugDir helper so we can point it to a tempdir."""
Mike Frysinger69cb41d2013-08-11 20:08:19 -040024
Alex Klein1699fab2022-09-08 08:46:06 -060025 TARGET = "chromite.scripts.cros_generate_breakpad_symbols"
26 ATTRS = ("FindDebugDir",)
27 DEFAULT_ATTR = "FindDebugDir"
Mike Frysinger69cb41d2013-08-11 20:08:19 -040028
Alex Klein1699fab2022-09-08 08:46:06 -060029 def __init__(self, path, *args, **kwargs):
30 self.path = path
31 super().__init__(*args, **kwargs)
Mike Frysinger69cb41d2013-08-11 20:08:19 -040032
Alex Klein1699fab2022-09-08 08:46:06 -060033 # pylint: disable=unused-argument
34 def FindDebugDir(self, _board, sysroot=None):
35 return self.path
Mike Frysinger69cb41d2013-08-11 20:08:19 -040036
37
Ian Barkley-Yeung5b485132023-07-25 16:54:31 -070038class IsSharedLibraryTest(cros_test_lib.TestCase):
39 """Test IsSharedLibrary"""
40
41 def testSharedLibaries(self):
42 """Verify that shared libraries return truthy"""
43 shared_libraries = [
44 "lib/libcontainer.so",
45 "lib64/libnss_db.so.2",
46 "usr/lib/libboost_type_erasure.so.1.81.0",
47 "usr/lib/v4l1compat.so",
48 ]
49
50 for shared_library in shared_libraries:
51 self.assertTrue(
52 cros_generate_breakpad_symbols.IsSharedLibrary(shared_library),
53 msg=f"expected {shared_library} to be a shared library",
54 )
55
56 def testExecutables(self):
57 """Verify that executables return None"""
58 executables = [
59 "sbin/crash_reporter",
60 "usr/bin/pqso", # ends in so but not .so
61 "usr/bin/perl5.36.0", # ends in numbers but not .so
62 ]
63
64 for executable in executables:
65 self.assertFalse(
66 cros_generate_breakpad_symbols.IsSharedLibrary(executable),
67 msg=f"expected {executable} to not be a shared library",
68 )
69
70
Mike Frysinger3ef6d972019-08-24 20:07:42 -040071# This long decorator triggers a false positive in the docstring test.
72# https://github.com/PyCQA/pylint/issues/3077
73# pylint: disable=bad-docstring-quotes
Alex Klein1699fab2022-09-08 08:46:06 -060074@mock.patch(
75 "chromite.scripts.cros_generate_breakpad_symbols." "GenerateBreakpadSymbol"
76)
Mike Frysinger69cb41d2013-08-11 20:08:19 -040077class GenerateSymbolsTest(cros_test_lib.MockTempDirTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -060078 """Test GenerateBreakpadSymbols."""
Mike Frysinger69cb41d2013-08-11 20:08:19 -040079
Alex Klein1699fab2022-09-08 08:46:06 -060080 def setUp(self):
81 self.board = "monkey-board"
82 self.board_dir = os.path.join(self.tempdir, "build", self.board)
83 self.debug_dir = os.path.join(self.board_dir, "usr", "lib", "debug")
84 self.breakpad_dir = os.path.join(self.debug_dir, "breakpad")
Mike Frysinger69cb41d2013-08-11 20:08:19 -040085
Alex Klein1699fab2022-09-08 08:46:06 -060086 # Generate a tree of files which we'll scan through.
87 elf_files = [
88 "bin/elf",
89 "iii/large-elf",
90 # Need some kernel modules (with & without matching .debug).
91 "lib/modules/3.10/module.ko",
92 "lib/modules/3.10/module-no-debug.ko",
93 # Need a file which has an ELF only, but not a .debug.
94 "usr/bin/elf-only",
95 "usr/sbin/elf",
96 ]
97 debug_files = [
98 "bin/bad-file",
99 "bin/elf.debug",
100 "iii/large-elf.debug",
101 "boot/vmlinux.debug",
102 "lib/modules/3.10/module.ko.debug",
103 # Need a file which has a .debug only, but not an ELF.
104 "sbin/debug-only.debug",
105 "usr/sbin/elf.debug",
106 ]
107 for f in [os.path.join(self.board_dir, x) for x in elf_files] + [
108 os.path.join(self.debug_dir, x) for x in debug_files
109 ]:
110 osutils.Touch(f, makedirs=True)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400111
Alex Klein1699fab2022-09-08 08:46:06 -0600112 # Set up random build dirs and symlinks.
113 buildid = os.path.join(self.debug_dir, ".build-id", "00")
114 osutils.SafeMakedirs(buildid)
115 os.symlink("/asdf", os.path.join(buildid, "foo"))
116 os.symlink("/bin/sh", os.path.join(buildid, "foo.debug"))
117 os.symlink("/bin/sh", os.path.join(self.debug_dir, "file.debug"))
118 osutils.WriteFile(
119 os.path.join(self.debug_dir, "iii", "large-elf.debug"),
120 "just some content",
121 )
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400122
Alex Klein1699fab2022-09-08 08:46:06 -0600123 self.StartPatcher(FindDebugDirMock(self.debug_dir))
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400124
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700125 def markAllFilesAsProcessed(self, gen_mock):
126 """Sets mock to pretend it processed all the expected ELF files.
127
128 This avoids having GenerateBreakpadSymbols return an error because not
129 all expected files were processed.
130 """
131 expected_found = list(cros_generate_breakpad_symbols.ALL_EXPECTED_FILES)
132
133 def _SetFound(*_args, **kwargs):
134 kwargs["found_files"].extend(expected_found)
135 gen_mock.side_effect = None
136 return 1
137
138 gen_mock.side_effect = _SetFound
139
Alex Klein1699fab2022-09-08 08:46:06 -0600140 def testNormal(self, gen_mock):
141 """Verify all the files we expect to get generated do"""
142 with parallel_unittest.ParallelMock():
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700143 self.markAllFilesAsProcessed(gen_mock)
Alex Klein1699fab2022-09-08 08:46:06 -0600144 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
145 self.board, sysroot=self.board_dir
146 )
147 self.assertEqual(ret, 0)
148 self.assertEqual(gen_mock.call_count, 5)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400149
Alex Klein1699fab2022-09-08 08:46:06 -0600150 # The largest ELF should be processed first.
151 call1 = (
152 os.path.join(self.board_dir, "iii/large-elf"),
153 os.path.join(self.debug_dir, "iii/large-elf.debug"),
154 )
155 self.assertEqual(gen_mock.call_args_list[0][0], call1)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400156
Alex Klein1699fab2022-09-08 08:46:06 -0600157 # The other ELFs can be called in any order.
158 call2 = (
159 os.path.join(self.board_dir, "bin/elf"),
160 os.path.join(self.debug_dir, "bin/elf.debug"),
161 )
162 call3 = (
163 os.path.join(self.board_dir, "usr/sbin/elf"),
164 os.path.join(self.debug_dir, "usr/sbin/elf.debug"),
165 )
166 call4 = (
167 os.path.join(self.board_dir, "lib/modules/3.10/module.ko"),
168 os.path.join(
169 self.debug_dir, "lib/modules/3.10/module.ko.debug"
170 ),
171 )
172 call5 = (
173 os.path.join(self.board_dir, "boot/vmlinux"),
174 os.path.join(self.debug_dir, "boot/vmlinux.debug"),
175 )
176 exp_calls = set((call2, call3, call4, call5))
177 actual_calls = set(
178 (
179 gen_mock.call_args_list[1][0],
180 gen_mock.call_args_list[2][0],
181 gen_mock.call_args_list[3][0],
182 gen_mock.call_args_list[4][0],
183 )
184 )
185 self.assertEqual(exp_calls, actual_calls)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400186
Alex Klein1699fab2022-09-08 08:46:06 -0600187 def testFileList(self, gen_mock):
188 """Verify that file_list restricts the symbols generated"""
189 with parallel_unittest.ParallelMock():
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700190 # Don't need markAllFilesAsProcessed since using file_list will
191 # skip the expected-files-processed check.
Alex Klein1699fab2022-09-08 08:46:06 -0600192 call1 = (
193 os.path.join(self.board_dir, "usr/sbin/elf"),
194 os.path.join(self.debug_dir, "usr/sbin/elf.debug"),
195 )
Prathmesh Prabhu9995e9b2013-10-31 16:43:55 -0700196
Alex Klein1699fab2022-09-08 08:46:06 -0600197 # Filter with elf path.
198 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
199 self.board,
200 sysroot=self.board_dir,
201 breakpad_dir=self.breakpad_dir,
202 file_list=[os.path.join(self.board_dir, "usr", "sbin", "elf")],
203 )
204 self.assertEqual(ret, 0)
205 self.assertEqual(gen_mock.call_count, 1)
206 self.assertEqual(gen_mock.call_args_list[0][0], call1)
Prathmesh Prabhu9995e9b2013-10-31 16:43:55 -0700207
Alex Klein1699fab2022-09-08 08:46:06 -0600208 # Filter with debug symbols file path.
209 gen_mock.reset_mock()
210 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
211 self.board,
212 sysroot=self.board_dir,
213 breakpad_dir=self.breakpad_dir,
214 file_list=[
215 os.path.join(self.debug_dir, "usr", "sbin", "elf.debug")
216 ],
217 )
218 self.assertEqual(ret, 0)
219 self.assertEqual(gen_mock.call_count, 1)
220 self.assertEqual(gen_mock.call_args_list[0][0], call1)
Prathmesh Prabhu9995e9b2013-10-31 16:43:55 -0700221
Alex Klein1699fab2022-09-08 08:46:06 -0600222 def testGenLimit(self, gen_mock):
223 """Verify generate_count arg works"""
224 with parallel_unittest.ParallelMock():
225 # Generate nothing!
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700226 # Don't need markAllFilesAsProcessed since using generate_count will
227 # skip the expected-files-processed check.
Alex Klein1699fab2022-09-08 08:46:06 -0600228 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
229 self.board,
230 sysroot=self.board_dir,
231 breakpad_dir=self.breakpad_dir,
232 generate_count=0,
233 )
234 self.assertEqual(ret, 0)
235 self.assertEqual(gen_mock.call_count, 0)
Prathmesh Prabhu9995e9b2013-10-31 16:43:55 -0700236
Alex Klein1699fab2022-09-08 08:46:06 -0600237 # Generate just one.
238 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
239 self.board,
240 sysroot=self.board_dir,
241 breakpad_dir=self.breakpad_dir,
242 generate_count=1,
243 )
244 self.assertEqual(ret, 0)
245 self.assertEqual(gen_mock.call_count, 1)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400246
Alex Klein1699fab2022-09-08 08:46:06 -0600247 # The largest ELF should be processed first.
248 call1 = (
249 os.path.join(self.board_dir, "iii/large-elf"),
250 os.path.join(self.debug_dir, "iii/large-elf.debug"),
251 )
252 self.assertEqual(gen_mock.call_args_list[0][0], call1)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400253
Alex Klein1699fab2022-09-08 08:46:06 -0600254 def testGenErrors(self, gen_mock):
255 """Verify we handle errors from generation correctly"""
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400256
Alex Klein1699fab2022-09-08 08:46:06 -0600257 def _SetError(*_args, **kwargs):
258 kwargs["num_errors"].value += 1
259 return 1
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400260
Alex Klein1699fab2022-09-08 08:46:06 -0600261 gen_mock.side_effect = _SetError
262 with parallel_unittest.ParallelMock():
263 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
264 self.board, sysroot=self.board_dir
265 )
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700266 # Expect 5 errors from calls plus 1 from the
267 # expected-files-processed check.
268 self.assertEqual(ret, 6)
Alex Klein1699fab2022-09-08 08:46:06 -0600269 self.assertEqual(gen_mock.call_count, 5)
Mike Frysinger9a628bb2013-10-24 15:51:37 -0400270
Alex Klein1699fab2022-09-08 08:46:06 -0600271 def testCleaningTrue(self, gen_mock):
272 """Verify behavior of clean_breakpad=True"""
273 with parallel_unittest.ParallelMock():
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700274 self.markAllFilesAsProcessed(gen_mock)
Alex Klein1699fab2022-09-08 08:46:06 -0600275 # Dir does not exist, and then does.
276 self.assertNotExists(self.breakpad_dir)
277 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
278 self.board,
279 sysroot=self.board_dir,
280 generate_count=1,
281 clean_breakpad=True,
282 )
283 self.assertEqual(ret, 0)
284 self.assertEqual(gen_mock.call_count, 1)
285 self.assertExists(self.breakpad_dir)
Mike Frysinger9a628bb2013-10-24 15:51:37 -0400286
Alex Klein1699fab2022-09-08 08:46:06 -0600287 # Dir exists before & after.
288 # File exists, but then doesn't.
289 stub_file = os.path.join(self.breakpad_dir, "fooooooooo")
290 osutils.Touch(stub_file)
291 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
292 self.board,
293 sysroot=self.board_dir,
294 generate_count=1,
295 clean_breakpad=True,
296 )
297 self.assertEqual(ret, 0)
298 self.assertEqual(gen_mock.call_count, 2)
299 self.assertNotExists(stub_file)
Mike Frysinger9a628bb2013-10-24 15:51:37 -0400300
Alex Klein1699fab2022-09-08 08:46:06 -0600301 def testCleaningFalse(self, gen_mock):
302 """Verify behavior of clean_breakpad=False"""
303 with parallel_unittest.ParallelMock():
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700304 self.markAllFilesAsProcessed(gen_mock)
Alex Klein1699fab2022-09-08 08:46:06 -0600305 # Dir does not exist, and then does.
306 self.assertNotExists(self.breakpad_dir)
307 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
308 self.board,
309 sysroot=self.board_dir,
310 generate_count=1,
311 clean_breakpad=False,
312 )
313 self.assertEqual(ret, 0)
314 self.assertEqual(gen_mock.call_count, 1)
315 self.assertExists(self.breakpad_dir)
Mike Frysinger9a628bb2013-10-24 15:51:37 -0400316
Alex Klein1699fab2022-09-08 08:46:06 -0600317 # Dir exists before & after.
318 # File exists before & after.
319 stub_file = os.path.join(self.breakpad_dir, "fooooooooo")
320 osutils.Touch(stub_file)
321 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
322 self.board,
323 sysroot=self.board_dir,
324 generate_count=1,
325 clean_breakpad=False,
326 )
327 self.assertEqual(ret, 0)
328 self.assertEqual(gen_mock.call_count, 2)
329 self.assertExists(stub_file)
330
331 def testExclusionList(self, gen_mock):
332 """Verify files in directories of the exclusion list are excluded"""
333 exclude_dirs = ["bin", "usr", "fake/dir/fake"]
334 with parallel_unittest.ParallelMock():
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700335 self.markAllFilesAsProcessed(gen_mock)
Alex Klein1699fab2022-09-08 08:46:06 -0600336 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
337 self.board, sysroot=self.board_dir, exclude_dirs=exclude_dirs
338 )
339 self.assertEqual(ret, 0)
340 self.assertEqual(gen_mock.call_count, 3)
341
Ian Barkley-Yeungb5274442023-04-28 16:32:20 -0700342 def testExpectedFilesCompleteFailure(self, _):
343 """Verify if no files are processed, all expected files give errors"""
344 with parallel_unittest.ParallelMock() and self.assertLogs(
345 level=logging.WARNING
346 ) as cm:
347 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
348 self.board, sysroot=self.board_dir
349 )
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700350 self.assertEqual(ret, 1)
Ian Barkley-Yeungb5274442023-04-28 16:32:20 -0700351 self.assertIn(
352 "Not all expected files were processed successfully",
353 "\n".join(cm.output),
354 )
355 for output in cm.output:
356 if (
357 "Not all expected files were processed successfully"
358 in output
359 ):
360 # This is the line that lists all the files we didn't find.
361 for (
362 expected_file
363 ) in cros_generate_breakpad_symbols.ExpectedFiles:
364 self.assertIn(expected_file.name, output)
365
366 def testExpectedFilesPartialFailure(self, gen_mock):
367 """If some expected files are processed, the others give errors"""
368 expected_found = (
369 cros_generate_breakpad_symbols.ExpectedFiles.LIBC,
370 cros_generate_breakpad_symbols.ExpectedFiles.CRASH_REPORTER,
371 )
372
373 def _SetFound(*_args, **kwargs):
374 kwargs["found_files"].extend(expected_found)
375 gen_mock.side_effect = None
376 return 1
377
378 gen_mock.side_effect = _SetFound
379 with parallel_unittest.ParallelMock() and self.assertLogs(
380 level=logging.WARNING
381 ) as cm:
382 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
383 self.board, sysroot=self.board_dir
384 )
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700385 self.assertEqual(ret, 1)
Ian Barkley-Yeungb5274442023-04-28 16:32:20 -0700386 self.assertIn(
387 "Not all expected files were processed successfully",
388 "\n".join(cm.output),
389 )
390 for output in cm.output:
391 if (
392 "Not all expected files were processed successfully"
393 in output
394 ):
395 # This is the line that lists all the files we didn't find.
396 for (
397 expected_file
398 ) in cros_generate_breakpad_symbols.ExpectedFiles:
399 if expected_file in expected_found:
400 self.assertNotIn(expected_file.name, output)
401 else:
402 self.assertIn(expected_file.name, output)
403
404 def testExpectedFilesWithSomeIgnored(self, _):
405 """If some expected files are ignored, they don't give errors"""
406 ignore_expected_files = [
407 cros_generate_breakpad_symbols.ExpectedFiles.ASH_CHROME,
408 cros_generate_breakpad_symbols.ExpectedFiles.LIBC,
409 ]
410 with parallel_unittest.ParallelMock() and self.assertLogs(
411 level=logging.WARNING
412 ) as cm:
413 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
414 self.board,
415 sysroot=self.board_dir,
416 ignore_expected_files=ignore_expected_files,
417 )
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700418 self.assertEqual(ret, 1)
Ian Barkley-Yeungb5274442023-04-28 16:32:20 -0700419 self.assertIn(
420 "Not all expected files were processed successfully",
421 "\n".join(cm.output),
422 )
423 for output in cm.output:
424 if (
425 "Not all expected files were processed successfully"
426 in output
427 ):
428 # This is the line that lists all the files we didn't find.
429 for (
430 expected_file
431 ) in cros_generate_breakpad_symbols.ExpectedFiles:
432 if expected_file in ignore_expected_files:
433 self.assertNotIn(expected_file.name, output)
434 else:
435 self.assertIn(expected_file.name, output)
436
437 def testExpectedFilesWithAllIgnored(self, _):
438 """If all expected files are ignored, there is no error"""
439 with parallel_unittest.ParallelMock() and self.assertLogs(
440 level=logging.WARNING
441 ) as cm:
442 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
443 self.board,
444 sysroot=self.board_dir,
445 ignore_expected_files=list(
446 cros_generate_breakpad_symbols.ExpectedFiles
447 ),
448 )
449 self.assertEqual(ret, 0)
450 self.assertNotIn(
451 "Not all expected files were processed successfully",
452 "\n".join(cm.output),
453 )
454
455 def testExpectedFilesWithSomeIgnoredAndSomeFound(self, gen_mock):
456 """Some expected files are ignored, others processed => no error"""
457 expected_found = (
458 cros_generate_breakpad_symbols.ExpectedFiles.LIBC,
459 cros_generate_breakpad_symbols.ExpectedFiles.CRASH_REPORTER,
460 )
461
462 def _SetFound(*_args, **kwargs):
463 kwargs["found_files"].extend(expected_found)
464 gen_mock.side_effect = None
465 return 1
466
467 gen_mock.side_effect = _SetFound
468 with parallel_unittest.ParallelMock() and self.assertLogs(
469 level=logging.WARNING
470 ) as cm:
471 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
472 self.board,
473 sysroot=self.board_dir,
474 ignore_expected_files=[
475 cros_generate_breakpad_symbols.ExpectedFiles.ASH_CHROME,
476 cros_generate_breakpad_symbols.ExpectedFiles.LIBMETRICS,
477 ],
478 )
479 self.assertEqual(ret, 0)
480 self.assertNotIn(
481 "Not all expected files were processed successfully",
482 "\n".join(cm.output),
483 )
484
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400485
Benjamin Gordon121a2aa2018-05-04 16:24:45 -0600486class GenerateSymbolTest(cros_test_lib.RunCommandTempDirTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600487 """Test GenerateBreakpadSymbol."""
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400488
Peter Boström763baca2023-05-25 16:36:33 +0000489 _DUMP_SYMS_BASE_CMD = ["dump_syms", "-v", "-d", "-m"]
490
Alex Klein1699fab2022-09-08 08:46:06 -0600491 def setUp(self):
492 self.elf_file = os.path.join(self.tempdir, "elf")
493 osutils.Touch(self.elf_file)
494 self.debug_dir = os.path.join(self.tempdir, "debug")
495 self.debug_file = os.path.join(self.debug_dir, "elf.debug")
496 osutils.Touch(self.debug_file, makedirs=True)
497 # Not needed as the code itself should create it as needed.
498 self.breakpad_dir = os.path.join(self.debug_dir, "breakpad")
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400499
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800500 self.FILE_OUT = (
501 f"{self.elf_file}: ELF 64-bit LSB pie executable, x86-64, "
502 "version 1 (SYSV), dynamically linked, interpreter "
503 "/lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, "
504 "BuildID[sha1]=cf9a21fa6b14bfb2dfcb76effd713c4536014d95, stripped"
505 )
Ian Barkley-Yeungc5b6f582023-03-08 18:23:07 -0800506 # A symbol file which would pass validation.
507 MINIMAL_SYMBOL_FILE = (
508 "MODULE OS CPU ID NAME\n"
509 "PUBLIC f10 0 func\n"
510 "STACK CFI INIT f10 22 .cfa: $rsp 8 + .ra: .cfa -8 + ^\n"
511 )
512 self.rc.SetDefaultCmdResult(stdout=MINIMAL_SYMBOL_FILE)
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800513 self.rc.AddCmdResult(
514 ["/usr/bin/file", self.elf_file], stdout=self.FILE_OUT
515 )
Alex Klein1699fab2022-09-08 08:46:06 -0600516 self.assertCommandContains = self.rc.assertCommandContains
517 self.sym_file = os.path.join(self.breakpad_dir, "NAME/ID/NAME.sym")
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400518
Alex Klein1699fab2022-09-08 08:46:06 -0600519 self.StartPatcher(FindDebugDirMock(self.debug_dir))
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400520
Alex Klein1699fab2022-09-08 08:46:06 -0600521 def assertCommandArgs(self, i, args):
522 """Helper for looking at the args of the |i|th call"""
523 self.assertEqual(self.rc.call_args_list[i][0][0], args)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400524
Alex Klein1699fab2022-09-08 08:46:06 -0600525 def testNormal(self):
526 """Normal run -- given an ELF and a debug file"""
527 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800528 self.elf_file,
529 self.debug_file,
530 self.breakpad_dir,
Alex Klein1699fab2022-09-08 08:46:06 -0600531 )
532 self.assertEqual(ret, self.sym_file)
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800533 self.assertEqual(self.rc.call_count, 2)
Alex Klein1699fab2022-09-08 08:46:06 -0600534 self.assertCommandArgs(
Peter Boström763baca2023-05-25 16:36:33 +0000535 1, self._DUMP_SYMS_BASE_CMD + [self.elf_file, self.debug_dir]
Alex Klein1699fab2022-09-08 08:46:06 -0600536 )
537 self.assertExists(self.sym_file)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400538
Alex Klein1699fab2022-09-08 08:46:06 -0600539 def testNormalNoCfi(self):
540 """Normal run w/out CFI"""
541 # Make sure the num_errors flag works too.
Trent Apted1e2e4f32023-05-05 03:50:20 +0000542 num_errors = ctypes.c_int(0)
Alex Klein1699fab2022-09-08 08:46:06 -0600543 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
544 self.elf_file,
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700545 self.debug_file,
Alex Klein1699fab2022-09-08 08:46:06 -0600546 breakpad_dir=self.breakpad_dir,
547 strip_cfi=True,
548 num_errors=num_errors,
549 )
550 self.assertEqual(ret, self.sym_file)
551 self.assertEqual(num_errors.value, 0)
Peter Boström763baca2023-05-25 16:36:33 +0000552 self.assertCommandArgs(
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700553 1, self._DUMP_SYMS_BASE_CMD + ["-c", self.elf_file, self.debug_dir]
Peter Boström763baca2023-05-25 16:36:33 +0000554 )
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800555 self.assertEqual(self.rc.call_count, 2)
Alex Klein1699fab2022-09-08 08:46:06 -0600556 self.assertExists(self.sym_file)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400557
Alex Klein1699fab2022-09-08 08:46:06 -0600558 def testNormalElfOnly(self):
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700559 """Normal run with just an ELF will fail"""
560 num_errors = ctypes.c_int(0)
Alex Klein1699fab2022-09-08 08:46:06 -0600561 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700562 self.elf_file,
563 breakpad_dir=self.breakpad_dir,
564 num_errors=num_errors,
Alex Klein1699fab2022-09-08 08:46:06 -0600565 )
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700566 self.assertEqual(ret, 1)
567 self.assertEqual(num_errors.value, 1)
568 self.assertNotExists(self.sym_file)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400569
Alex Klein1699fab2022-09-08 08:46:06 -0600570 def testNormalSudo(self):
571 """Normal run where ELF is readable only by root"""
572 with mock.patch.object(os, "access") as mock_access:
573 mock_access.return_value = False
574 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700575 self.elf_file, self.debug_file, breakpad_dir=self.breakpad_dir
Alex Klein1699fab2022-09-08 08:46:06 -0600576 )
577 self.assertEqual(ret, self.sym_file)
578 self.assertCommandArgs(
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700579 1,
580 ["sudo", "--"]
581 + self._DUMP_SYMS_BASE_CMD
582 + [self.elf_file, self.debug_dir],
Alex Klein1699fab2022-09-08 08:46:06 -0600583 )
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400584
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700585 def testDumpSymsFail(self):
586 """The call to dump_syms failed"""
Alex Klein1699fab2022-09-08 08:46:06 -0600587 self.rc.AddCmdResult(
Peter Boström763baca2023-05-25 16:36:33 +0000588 self._DUMP_SYMS_BASE_CMD + [self.elf_file, self.debug_dir],
589 returncode=1,
Alex Klein1699fab2022-09-08 08:46:06 -0600590 )
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700591 num_errors = ctypes.c_int(0)
Alex Klein1699fab2022-09-08 08:46:06 -0600592 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700593 self.elf_file,
594 self.debug_file,
595 breakpad_dir=self.breakpad_dir,
596 num_errors=num_errors,
Alex Klein1699fab2022-09-08 08:46:06 -0600597 )
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700598 self.assertEqual(ret, 1)
599 self.assertEqual(num_errors.value, 1)
600 self.assertEqual(self.rc.call_count, 2)
Alex Klein1699fab2022-09-08 08:46:06 -0600601 self.assertCommandArgs(
Peter Boström763baca2023-05-25 16:36:33 +0000602 1, self._DUMP_SYMS_BASE_CMD + [self.elf_file, self.debug_dir]
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800603 )
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700604 self.assertNotExists(self.sym_file)
605
606 def testValidationFail(self):
607 """If symbol file validation fails, return an error"""
608 BAD_SYMBOL_FILE = "MODULE OS CPU ID NAME\n"
609 self.rc.SetDefaultCmdResult(stdout=BAD_SYMBOL_FILE)
610 num_errors = ctypes.c_int(0)
611 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
612 self.elf_file,
613 self.debug_file,
614 breakpad_dir=self.breakpad_dir,
615 strip_cfi=True,
616 num_errors=num_errors,
Alex Klein1699fab2022-09-08 08:46:06 -0600617 )
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700618 self.assertEqual(ret, 1)
619 self.assertEqual(num_errors.value, 1)
620 self.assertNotExists(self.sym_file)
621
622 def testValidationRaises(self):
623 """If symbol file validation raises an error, return an error."""
624 BAD_SYMBOL_FILE = (
625 "MODULE Linux x86 D3096ED481217FD4C16B29CD9BC208BA0 elf\n"
626 "JUNK LINE IS BAD LINE\n"
Alex Klein1699fab2022-09-08 08:46:06 -0600627 )
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700628 self.rc.SetDefaultCmdResult(stdout=BAD_SYMBOL_FILE)
629 num_errors = ctypes.c_int(0)
630 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
631 self.elf_file,
632 self.debug_file,
633 breakpad_dir=self.breakpad_dir,
634 strip_cfi=True,
635 num_errors=num_errors,
636 )
637 self.assertEqual(ret, 1)
638 self.assertEqual(num_errors.value, 1)
639 self.assertNotExists(self.sym_file)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400640
Ian Barkley-Yeungb5274442023-04-28 16:32:20 -0700641 def testForceBasicFallback(self):
642 """Running with force_basic_fallback
643
644 Test force_basic_fallback goes straight to _DumpAllowingBasicFallback().
645 """
646 self.rc.AddCmdResult(
Peter Boström763baca2023-05-25 16:36:33 +0000647 self._DUMP_SYMS_BASE_CMD + [self.elf_file, self.debug_dir],
648 returncode=1,
Ian Barkley-Yeungb5274442023-04-28 16:32:20 -0700649 )
650 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
651 self.elf_file,
652 self.debug_file,
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700653 breakpad_dir=self.breakpad_dir,
Ian Barkley-Yeungb5274442023-04-28 16:32:20 -0700654 force_basic_fallback=True,
655 )
656 self.assertEqual(ret, self.sym_file)
657 self.assertEqual(self.rc.call_count, 2)
Ian Barkley-Yeungb5274442023-04-28 16:32:20 -0700658 self.assertCommandArgs(
Peter Boström763baca2023-05-25 16:36:33 +0000659 0, self._DUMP_SYMS_BASE_CMD + [self.elf_file, self.debug_dir]
Ian Barkley-Yeungb5274442023-04-28 16:32:20 -0700660 )
661 self.assertCommandArgs(
Peter Boström763baca2023-05-25 16:36:33 +0000662 1,
663 self._DUMP_SYMS_BASE_CMD
664 + ["-c", "-r", self.elf_file, self.debug_dir],
Ian Barkley-Yeungb5274442023-04-28 16:32:20 -0700665 )
666 self.assertExists(self.sym_file)
667
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700668 def testForceBasicFallbackElfOnly(self):
669 """Running with force_basic_fallback run given just an ELF"""
670 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
671 self.elf_file,
672 breakpad_dir=self.breakpad_dir,
673 force_basic_fallback=True,
674 )
675 self.assertEqual(ret, self.sym_file)
676 self.assertEqual(self.rc.call_count, 1)
677 self.assertCommandArgs(0, self._DUMP_SYMS_BASE_CMD + [self.elf_file])
678 self.assertExists(self.sym_file)
679
680 def testForceBasicFallbackLargeDebugFail(self):
681 """In fallback mode, running w/large .debug failed, but retry worked"""
682 self.rc.AddCmdResult(
683 self._DUMP_SYMS_BASE_CMD + [self.elf_file, self.debug_dir],
684 returncode=1,
685 )
686 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
687 self.elf_file,
688 self.debug_file,
689 breakpad_dir=self.breakpad_dir,
690 force_basic_fallback=True,
691 )
692 self.assertEqual(ret, self.sym_file)
693 self.assertEqual(self.rc.call_count, 2)
694 self.assertCommandArgs(
695 0, self._DUMP_SYMS_BASE_CMD + [self.elf_file, self.debug_dir]
696 )
697 self.assertCommandArgs(
698 1,
699 self._DUMP_SYMS_BASE_CMD
700 + ["-c", "-r", self.elf_file, self.debug_dir],
701 )
702 self.assertExists(self.sym_file)
703
704 def testForceBasicFallbackDebugFail(self):
705 """In fallback mode, running w/.debug always fails, but works without"""
Alex Klein1699fab2022-09-08 08:46:06 -0600706 self.rc.AddCmdResult(
Peter Boström763baca2023-05-25 16:36:33 +0000707 self._DUMP_SYMS_BASE_CMD + [self.elf_file, self.debug_dir],
708 returncode=1,
Alex Klein1699fab2022-09-08 08:46:06 -0600709 )
710 self.rc.AddCmdResult(
Peter Boström763baca2023-05-25 16:36:33 +0000711 self._DUMP_SYMS_BASE_CMD
712 + ["-c", "-r", self.elf_file, self.debug_dir],
Alex Klein1699fab2022-09-08 08:46:06 -0600713 returncode=1,
714 )
715 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700716 self.elf_file,
717 self.debug_file,
718 breakpad_dir=self.breakpad_dir,
719 force_basic_fallback=True,
Alex Klein1699fab2022-09-08 08:46:06 -0600720 )
721 self.assertEqual(ret, self.sym_file)
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700722 self.assertEqual(self.rc.call_count, 3)
Alex Klein1699fab2022-09-08 08:46:06 -0600723 self.assertCommandArgs(
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700724 0, self._DUMP_SYMS_BASE_CMD + [self.elf_file, self.debug_dir]
Alex Klein1699fab2022-09-08 08:46:06 -0600725 )
726 self.assertCommandArgs(
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700727 1,
Peter Boström763baca2023-05-25 16:36:33 +0000728 self._DUMP_SYMS_BASE_CMD
729 + ["-c", "-r", self.elf_file, self.debug_dir],
Alex Klein1699fab2022-09-08 08:46:06 -0600730 )
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700731 self.assertCommandArgs(2, self._DUMP_SYMS_BASE_CMD + [self.elf_file])
Alex Klein1699fab2022-09-08 08:46:06 -0600732 self.assertExists(self.sym_file)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400733
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700734 def testForceBasicFallbackCompleteFail(self):
735 """In fallback mode, if dump_syms always fails, still an error"""
Alex Klein1699fab2022-09-08 08:46:06 -0600736 self.rc.SetDefaultCmdResult(returncode=1)
737 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
Ian Barkley-Yeung9c9430e2023-08-09 17:24:18 -0700738 self.elf_file,
739 breakpad_dir=self.breakpad_dir,
740 force_basic_fallback=True,
Alex Klein1699fab2022-09-08 08:46:06 -0600741 )
742 self.assertEqual(ret, 1)
743 # Make sure the num_errors flag works too.
Trent Apted1e2e4f32023-05-05 03:50:20 +0000744 num_errors = ctypes.c_int(0)
Alex Klein1699fab2022-09-08 08:46:06 -0600745 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
746 self.elf_file, breakpad_dir=self.breakpad_dir, num_errors=num_errors
747 )
748 self.assertEqual(ret, 1)
749 self.assertEqual(num_errors.value, 1)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400750
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800751 def testKernelObjects(self):
752 """Kernel object files should call _DumpAllowingBasicFallback()"""
753 ko_file = os.path.join(self.tempdir, "elf.ko")
754 osutils.Touch(ko_file)
755 self.rc.AddCmdResult(
Peter Boström763baca2023-05-25 16:36:33 +0000756 self._DUMP_SYMS_BASE_CMD + [ko_file, self.debug_dir],
757 returncode=1,
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800758 )
759 self.rc.AddCmdResult(
Peter Boström763baca2023-05-25 16:36:33 +0000760 self._DUMP_SYMS_BASE_CMD + ["-c", "-r", ko_file, self.debug_dir],
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800761 returncode=1,
762 )
763 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
764 ko_file, self.debug_file, self.breakpad_dir
765 )
766 self.assertEqual(ret, self.sym_file)
767 self.assertEqual(self.rc.call_count, 3)
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800768 self.assertCommandArgs(
Peter Boström763baca2023-05-25 16:36:33 +0000769 0, self._DUMP_SYMS_BASE_CMD + [ko_file, self.debug_dir]
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800770 )
Peter Boström763baca2023-05-25 16:36:33 +0000771 self.assertCommandArgs(
772 1,
773 self._DUMP_SYMS_BASE_CMD + ["-c", "-r", ko_file, self.debug_dir],
774 )
775 self.assertCommandArgs(2, self._DUMP_SYMS_BASE_CMD + [ko_file])
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800776 self.assertExists(self.sym_file)
777
778 def testGoBinary(self):
779 """Go binaries should call _DumpAllowingBasicFallback()
780
781 Also tests that dump_syms failing with 'file contains no debugging
782 information' does not fail the script.
783 """
784 go_binary = os.path.join(self.tempdir, "goprogram")
785 osutils.Touch(go_binary)
786 go_debug_file = os.path.join(self.debug_dir, "goprogram.debug")
787 osutils.Touch(go_debug_file, makedirs=True)
788 FILE_OUT_GO = go_binary + (
789 ": ELF 64-bit LSB executable, x86-64, "
790 "version 1 (SYSV), statically linked, "
Ian Barkley-Yeungc5b6f582023-03-08 18:23:07 -0800791 "Go BuildID=KKXVlL66E8Qmngr4qll9/5kOKGZw9I7TmNhoqKLqq/SiYVJam6w5Fo"
792 "39B3BtDo/ba8_ceezZ-3R4qEv6_-K, not stripped"
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800793 )
794 self.rc.AddCmdResult(["/usr/bin/file", go_binary], stdout=FILE_OUT_GO)
795 self.rc.AddCmdResult(
Peter Boström763baca2023-05-25 16:36:33 +0000796 self._DUMP_SYMS_BASE_CMD + [go_binary, self.debug_dir],
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800797 returncode=1,
798 )
799 self.rc.AddCmdResult(
Peter Boström763baca2023-05-25 16:36:33 +0000800 self._DUMP_SYMS_BASE_CMD + ["-c", "-r", go_binary, self.debug_dir],
801 returncode=1,
802 )
803 self.rc.AddCmdResult(
804 self._DUMP_SYMS_BASE_CMD + [go_binary],
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800805 returncode=1,
806 stderr=(
807 f"{go_binary}: file contains no debugging information "
808 '(no ".stab" or ".debug_info" sections)'
809 ),
810 )
Trent Apted1e2e4f32023-05-05 03:50:20 +0000811 num_errors = ctypes.c_int(0)
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800812 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
813 go_binary, go_debug_file, self.breakpad_dir
814 )
815 self.assertEqual(ret, 0)
816 self.assertEqual(self.rc.call_count, 4)
817 self.assertCommandArgs(0, ["/usr/bin/file", go_binary])
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800818 self.assertCommandArgs(
Peter Boström763baca2023-05-25 16:36:33 +0000819 1, self._DUMP_SYMS_BASE_CMD + [go_binary, self.debug_dir]
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800820 )
821 self.assertCommandArgs(
Peter Boström763baca2023-05-25 16:36:33 +0000822 2,
823 self._DUMP_SYMS_BASE_CMD + ["-c", "-r", go_binary, self.debug_dir],
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800824 )
Peter Boström763baca2023-05-25 16:36:33 +0000825 self.assertCommandArgs(3, self._DUMP_SYMS_BASE_CMD + [go_binary])
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800826 self.assertNotExists(self.sym_file)
827 self.assertEqual(num_errors.value, 0)
828
Ian Barkley-Yeunge1785762023-03-08 18:32:18 -0800829 def _testBinaryIsInLocalFallback(self, directory, filename):
830 binary = os.path.join(self.tempdir, directory, filename)
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800831 osutils.Touch(binary, makedirs=True)
Ian Barkley-Yeunge1785762023-03-08 18:32:18 -0800832 debug_dir = os.path.join(self.debug_dir, directory)
833 debug_file = os.path.join(debug_dir, f"{filename}.debug")
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800834 osutils.Touch(debug_file, makedirs=True)
835 self.rc.AddCmdResult(["/usr/bin/file", binary], stdout=self.FILE_OUT)
836 self.rc.AddCmdResult(
Peter Boström763baca2023-05-25 16:36:33 +0000837 self._DUMP_SYMS_BASE_CMD + [binary, debug_dir], returncode=1
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800838 )
839 self.rc.AddCmdResult(
Peter Boström763baca2023-05-25 16:36:33 +0000840 self._DUMP_SYMS_BASE_CMD + ["-c", "-r", binary, debug_dir],
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800841 returncode=1,
842 )
843 self.rc.AddCmdResult(
Peter Boström763baca2023-05-25 16:36:33 +0000844 self._DUMP_SYMS_BASE_CMD + [binary],
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800845 returncode=1,
846 stderr=(
847 f"{binary}: file contains no debugging information "
848 '(no ".stab" or ".debug_info" sections)'
849 ),
850 )
Trent Apted1e2e4f32023-05-05 03:50:20 +0000851 num_errors = ctypes.c_int(0)
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800852 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
853 binary, debug_file, self.breakpad_dir, sysroot=self.tempdir
854 )
855 self.assertEqual(ret, 0)
856 self.assertEqual(self.rc.call_count, 4)
857 self.assertCommandArgs(0, ["/usr/bin/file", binary])
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800858 self.assertCommandArgs(
Peter Boström763baca2023-05-25 16:36:33 +0000859 1, self._DUMP_SYMS_BASE_CMD + [binary, debug_dir]
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800860 )
Peter Boström763baca2023-05-25 16:36:33 +0000861 self.assertCommandArgs(
862 2, self._DUMP_SYMS_BASE_CMD + ["-c", "-r", binary, debug_dir]
863 )
864 self.assertCommandArgs(3, self._DUMP_SYMS_BASE_CMD + [binary])
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800865 self.assertNotExists(self.sym_file)
866 self.assertEqual(num_errors.value, 0)
867
Ian Barkley-Yeunge1785762023-03-08 18:32:18 -0800868 def testAllowlist(self):
869 """Binaries in the allowlist should call _DumpAllowingBasicFallback()"""
870 self._testBinaryIsInLocalFallback("usr/bin", "goldctl")
871
872 def testUsrLocalSkip(self):
873 """Binaries in /usr/local should call _DumpAllowingBasicFallback()"""
874 self._testBinaryIsInLocalFallback("usr/local", "minidump_stackwalk")
875
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400876
Ian Barkley-Yeungc5b6f582023-03-08 18:23:07 -0800877class ValidateSymbolFileTest(cros_test_lib.TempDirTestCase):
878 """Tests ValidateSymbolFile"""
879
880 def _GetTestdataFile(self, filename: str) -> str:
881 """Gets the path to a file in the testdata directory.
882
883 Args:
884 filename: The base filename of the file.
885
886 Returns:
887 A string with the complete path to the file.
888 """
889 return os.path.join(os.path.dirname(__file__), "testdata", filename)
890
891 def testValidSymbolFiles(self):
892 """Make sure ValidateSymbolFile passes on valid files"""
893
894 # All files are in the testdata/ subdirectory.
895 VALID_SYMBOL_FILES = [
896 # A "normal" symbol file from an executable.
897 "basic.sym",
898 # A "normal" symbol file from a shared library.
899 "basic_lib.sym",
900 # A symbol file with PUBLIC records but no FUNC records.
901 "public_only.sym",
902 # A symbol file with FUNC records but no PUBLIC records.
903 "func_only.sym",
904 # A symbol file with at least one of every line type.
905 "all_line_types.sym",
906 ]
907
908 for file in VALID_SYMBOL_FILES:
909 with self.subTest(
910 file=file
911 ), multiprocessing.Manager() as mp_manager:
912 found_files = mp_manager.list()
913 self.assertTrue(
914 cros_generate_breakpad_symbols.ValidateSymbolFile(
915 self._GetTestdataFile(file),
916 "/build/board/bin/foo",
917 "/build/board",
918 found_files,
919 )
920 )
921 self.assertFalse(found_files)
922
923 def testInvalidSymbolFiles(self):
924 """Make sure ValidateSymbolFile fails on invalid files.
925
926 This test only covers cases that return false, not cases that raise
927 exceptions.
928 """
929
930 class InvalidSymbolFile:
931 """The name of an invalid symbol file + the expected error msg."""
932
933 def __init__(self, filename, expected_errors):
934 self.filename = filename
935 self.expected_errors = expected_errors
936
937 INVALID_SYMBOL_FILES = [
938 InvalidSymbolFile(
939 "bad_no_func_or_public.sym",
940 [
941 "WARNING:root:/build/board/bin/foo: "
942 "Symbol file has no FUNC or PUBLIC records"
943 ],
944 ),
945 InvalidSymbolFile(
946 "bad_no_stack.sym",
947 [
948 "WARNING:root:/build/board/bin/foo: "
949 "Symbol file has no STACK records"
950 ],
951 ),
952 InvalidSymbolFile(
953 "bad_no_module.sym",
954 [
955 "WARNING:root:/build/board/bin/foo: "
956 "Symbol file has 0 MODULE lines"
957 ],
958 ),
959 InvalidSymbolFile(
960 "bad_two_modules.sym",
961 [
962 "WARNING:root:/build/board/bin/foo: "
963 "Symbol file has 2 MODULE lines"
964 ],
965 ),
966 InvalidSymbolFile(
967 "bad_func_no_line_numbers.sym",
968 [
969 "WARNING:root:/build/board/bin/foo: "
970 "Symbol file has FUNC records but no line numbers"
971 ],
972 ),
973 InvalidSymbolFile(
974 "bad_line_numbers_no_file.sym",
975 [
976 "WARNING:root:/build/board/bin/foo: "
977 "Symbol file has line number records but no FILE records"
978 ],
979 ),
980 InvalidSymbolFile(
981 "bad_inline_no_files.sym",
982 [
983 "WARNING:root:/build/board/bin/foo: "
984 "Symbol file has INLINE records but no FILE records"
985 ],
986 ),
987 InvalidSymbolFile(
988 "bad_inline_no_origins.sym",
989 [
990 "WARNING:root:/build/board/bin/foo: "
991 "Symbol file has INLINE records but no INLINE_ORIGIN "
992 "records"
993 ],
994 ),
995 InvalidSymbolFile(
996 "blank.sym",
997 [
998 "WARNING:root:/build/board/bin/foo: "
999 "Symbol file has no STACK records",
1000 "WARNING:root:/build/board/bin/foo: "
1001 "Symbol file has 0 MODULE lines",
1002 "WARNING:root:/build/board/bin/foo: "
1003 "Symbol file has no FUNC or PUBLIC records",
1004 ],
1005 ),
1006 ]
1007
1008 for file in INVALID_SYMBOL_FILES:
1009 with self.subTest(
1010 file=file.filename
1011 ), multiprocessing.Manager() as mp_manager:
1012 found_files = mp_manager.list()
1013 with self.assertLogs(level=logging.WARNING) as cm:
1014 self.assertFalse(
1015 cros_generate_breakpad_symbols.ValidateSymbolFile(
1016 self._GetTestdataFile(file.filename),
1017 "/build/board/bin/foo",
1018 "/build/board",
1019 found_files,
1020 )
1021 )
1022 self.assertEqual(file.expected_errors, cm.output)
1023 self.assertFalse(found_files)
1024
1025 def testInvalidSymbolFilesWhichRaise(self):
1026 """Test ValidateSymbolFile raise exceptions on certain files"""
1027
1028 class InvalidSymbolFile:
1029 """The invalid symbol file + the expected exception message"""
1030
1031 def __init__(self, filename, expected_exception_regex):
1032 self.filename = filename
1033 self.expected_exception_regex = expected_exception_regex
1034
1035 INVALID_SYMBOL_FILES = [
1036 InvalidSymbolFile(
1037 "bad_unknown_line_type.sym",
Ian Barkley-Yeungccfa1b52023-07-07 18:55:46 -07001038 r"symbol file has unknown line type UNKNOWN "
1039 r"\(line='UNKNOWN line type\n'\)",
Ian Barkley-Yeungc5b6f582023-03-08 18:23:07 -08001040 ),
1041 InvalidSymbolFile(
1042 "bad_blank_line.sym",
1043 "symbol file has unexpected blank line",
1044 ),
1045 InvalidSymbolFile(
1046 "bad_short_func.sym",
1047 r"symbol file has FUNC line with 2 words "
Ian Barkley-Yeungccfa1b52023-07-07 18:55:46 -07001048 r"\(expected 5 or more\) \(line='FUNC fb0\n'\)",
Ian Barkley-Yeungc5b6f582023-03-08 18:23:07 -08001049 ),
1050 InvalidSymbolFile(
1051 "bad_short_line_number.sym",
1052 r"symbol file has line number line with 3 words "
Ian Barkley-Yeungccfa1b52023-07-07 18:55:46 -07001053 r"\(expected 4 - 4\) \(line='fb0 106 0\n'\)",
Ian Barkley-Yeungc5b6f582023-03-08 18:23:07 -08001054 ),
1055 InvalidSymbolFile(
1056 "bad_long_line_number.sym",
1057 r"symbol file has line number line with 5 words "
Ian Barkley-Yeungccfa1b52023-07-07 18:55:46 -07001058 r"\(expected 4 - 4\) \(line='c184 7 59 4 8\n'\)",
Ian Barkley-Yeungc5b6f582023-03-08 18:23:07 -08001059 ),
1060 ]
1061
1062 for file in INVALID_SYMBOL_FILES:
1063 with self.subTest(
1064 file=file.filename
1065 ), multiprocessing.Manager() as mp_manager:
1066 found_files = mp_manager.list()
1067 self.assertRaisesRegex(
1068 ValueError,
1069 file.expected_exception_regex,
1070 cros_generate_breakpad_symbols.ValidateSymbolFile,
1071 self._GetTestdataFile(file.filename),
1072 "/build/board/bin/foo",
1073 "/build/board",
1074 found_files,
1075 )
1076
1077 def testAllowlist(self):
1078 """Test that ELFs on the allowlist are allowed to pass."""
1079 with multiprocessing.Manager() as mp_manager:
1080 found_files = mp_manager.list()
1081 self.assertTrue(
1082 cros_generate_breakpad_symbols.ValidateSymbolFile(
Ian Barkley-Yeung5b485132023-07-25 16:54:31 -07001083 self._GetTestdataFile("bad_no_module.sym"),
Ian Barkley-Yeungc5b6f582023-03-08 18:23:07 -08001084 "/build/board/opt/google/chrome/nacl_helper_bootstrap",
1085 "/build/board",
1086 found_files,
1087 )
1088 )
1089 self.assertFalse(found_files)
1090
1091 def testAllowlistRegex(self):
1092 """Test that ELFs on the regex-based allowlist are allowed to pass."""
1093 with multiprocessing.Manager() as mp_manager:
1094 found_files = mp_manager.list()
1095 self.assertTrue(
1096 cros_generate_breakpad_symbols.ValidateSymbolFile(
Ian Barkley-Yeung5b485132023-07-25 16:54:31 -07001097 self._GetTestdataFile("bad_no_module.sym"),
1098 "/build/board/lib64/libnss_dns.so.2",
1099 "/build/board",
1100 found_files,
1101 )
1102 )
1103 self.assertFalse(found_files)
1104
1105 def testSharedLibrariesSkipStackTest(self):
1106 """Test that shared libraries can pass validation with no STACK."""
1107 with multiprocessing.Manager() as mp_manager:
1108 found_files = mp_manager.list()
1109 self.assertTrue(
1110 cros_generate_breakpad_symbols.ValidateSymbolFile(
Ian Barkley-Yeungc5b6f582023-03-08 18:23:07 -08001111 self._GetTestdataFile("bad_no_stack.sym"),
Ian Barkley-Yeung5b485132023-07-25 16:54:31 -07001112 "/build/board/lib64/libiw.so.30",
Ian Barkley-Yeungc5b6f582023-03-08 18:23:07 -08001113 "/build/board",
1114 found_files,
1115 )
1116 )
1117 self.assertFalse(found_files)
1118
1119 def _CreateSymbolFile(
1120 self,
1121 sym_file: pathlib.Path,
1122 func_lines: int = 0,
1123 public_lines: int = 0,
1124 stack_lines: int = 0,
1125 line_number_lines: int = 0,
1126 ) -> None:
1127 """Creates a symbol file.
1128
1129 Creates a symbol file with the given number of lines (and enough other
1130 lines to pass validation) in the temp directory.
1131
1132 To pass validation, chrome.sym files must be huge; create them
1133 programmatically during the test instead of checking in a real 800MB+
1134 chrome symbol file.
1135 """
1136 with sym_file.open(mode="w", encoding="utf-8") as f:
1137 f.write("MODULE OS CPU ID NAME\n")
1138 f.write("FILE 0 /path/to/source.cc\n")
1139 for func in range(0, func_lines):
1140 f.write(f"FUNC {func} 1 0 function{func}\n")
1141 for public in range(0, public_lines):
1142 f.write(f"PUBLIC {public} 0 Public{public}\n")
1143 for line in range(0, line_number_lines):
1144 f.write(f"{line} 1 {line} 0\n")
1145 for stack in range(0, stack_lines):
1146 f.write(f"STACK CFI {stack} .cfa: $esp {stack} +\n")
1147
1148 def testValidChromeSymbolFile(self):
1149 """Test that a chrome symbol file can pass the additional checks"""
1150 sym_file = self.tempdir / "chrome.sym"
1151 self._CreateSymbolFile(
1152 sym_file,
1153 func_lines=100000,
1154 public_lines=10,
1155 stack_lines=1000000,
1156 line_number_lines=1000000,
1157 )
1158 with multiprocessing.Manager() as mp_manager:
1159 found_files = mp_manager.list()
1160 self.assertTrue(
1161 cros_generate_breakpad_symbols.ValidateSymbolFile(
1162 str(sym_file),
1163 "/build/board/opt/google/chrome/chrome",
1164 "/build/board",
1165 found_files,
1166 )
1167 )
1168 self.assertEqual(
1169 list(found_files),
1170 [cros_generate_breakpad_symbols.ExpectedFiles.ASH_CHROME],
1171 )
1172
1173 def testInvalidChromeSymbolFile(self):
1174 """Test that a chrome symbol file is held to higher standards."""
1175
1176 class ChromeSymbolFileTest:
1177 """Defines the subtest for an invalid Chrome symbol file."""
1178
1179 def __init__(
1180 self,
1181 name,
1182 expected_error,
1183 func_lines=100000,
1184 stack_lines=1000000,
1185 line_number_lines=1000000,
1186 ):
1187 self.name = name
1188 self.expected_error = expected_error
1189 self.func_lines = func_lines
1190 self.stack_lines = stack_lines
1191 self.line_number_lines = line_number_lines
1192
1193 CHROME_SYMBOL_TESTS = [
1194 ChromeSymbolFileTest(
1195 name="Insufficient FUNC records",
1196 func_lines=10000,
1197 expected_error="chrome should have at least 100,000 FUNC "
1198 "records, found 10000",
1199 ),
1200 ChromeSymbolFileTest(
1201 name="Insufficient STACK records",
1202 stack_lines=100000,
1203 expected_error="chrome should have at least 1,000,000 STACK "
1204 "records, found 100000",
1205 ),
1206 ChromeSymbolFileTest(
1207 name="Insufficient line number records",
1208 line_number_lines=100000,
1209 expected_error="chrome should have at least 1,000,000 "
1210 "line number records, found 100000",
1211 ),
1212 ]
1213 for test in CHROME_SYMBOL_TESTS:
1214 with self.subTest(
1215 name=test.name
1216 ), multiprocessing.Manager() as mp_manager:
1217 sym_file = self.tempdir / "chrome.sym"
1218 self._CreateSymbolFile(
1219 sym_file,
1220 func_lines=test.func_lines,
1221 public_lines=10,
1222 stack_lines=test.stack_lines,
1223 line_number_lines=test.line_number_lines,
1224 )
1225 found_files = mp_manager.list()
1226 with self.assertLogs(level=logging.WARNING) as cm:
1227 self.assertFalse(
1228 cros_generate_breakpad_symbols.ValidateSymbolFile(
1229 str(sym_file),
1230 "/build/board/opt/google/chrome/chrome",
1231 "/build/board",
1232 found_files,
1233 )
1234 )
1235 self.assertIn(test.expected_error, cm.output[0])
1236 self.assertEqual(len(cm.output), 1)
1237
1238 def testValidLibcSymbolFile(self):
1239 """Test that a libc.so symbol file can pass the additional checks."""
1240 with multiprocessing.Manager() as mp_manager:
1241 sym_file = self.tempdir / "libc.so.sym"
1242 self._CreateSymbolFile(
1243 sym_file, public_lines=200, stack_lines=20000
1244 )
1245 found_files = mp_manager.list()
1246 self.assertTrue(
1247 cros_generate_breakpad_symbols.ValidateSymbolFile(
1248 str(sym_file),
1249 "/build/board/lib64/libc.so.6",
1250 "/build/board",
1251 found_files,
1252 )
1253 )
1254 self.assertEqual(
1255 list(found_files),
1256 [cros_generate_breakpad_symbols.ExpectedFiles.LIBC],
1257 )
1258
1259 def testInvalidLibcSymbolFile(self):
1260 """Test that a libc.so symbol file is held to higher standards."""
1261
1262 class LibcSymbolFileTest:
1263 """Defines the subtest for an invalid libc symbol file."""
1264
1265 def __init__(
1266 self,
1267 name,
1268 expected_error,
1269 public_lines=200,
1270 stack_lines=20000,
1271 ):
1272 self.name = name
1273 self.expected_error = expected_error
1274 self.public_lines = public_lines
1275 self.stack_lines = stack_lines
1276
1277 LIBC_SYMBOL_TESTS = [
1278 LibcSymbolFileTest(
1279 name="Insufficient PUBLIC records",
1280 public_lines=50,
1281 expected_error="/build/board/lib64/libc.so.6 should have at "
1282 "least 100 PUBLIC records, found 50",
1283 ),
1284 LibcSymbolFileTest(
1285 name="Insufficient STACK records",
1286 stack_lines=1000,
1287 expected_error="/build/board/lib64/libc.so.6 should have at "
1288 "least 10000 STACK records, found 1000",
1289 ),
1290 ]
1291 for test in LIBC_SYMBOL_TESTS:
1292 with self.subTest(
1293 name=test.name
1294 ), multiprocessing.Manager() as mp_manager:
1295 sym_file = self.tempdir / "libc.so.sym"
1296 self._CreateSymbolFile(
1297 sym_file,
1298 public_lines=test.public_lines,
1299 stack_lines=test.stack_lines,
1300 )
1301 found_files = mp_manager.list()
1302 with self.assertLogs(level=logging.WARNING) as cm:
1303 self.assertFalse(
1304 cros_generate_breakpad_symbols.ValidateSymbolFile(
1305 str(sym_file),
1306 "/build/board/lib64/libc.so.6",
1307 "/build/board",
1308 found_files,
1309 )
1310 )
1311 self.assertIn(test.expected_error, cm.output[0])
1312 self.assertEqual(len(cm.output), 1)
1313
1314 def testValidCrashReporterSymbolFile(self):
1315 """Test a crash_reporter symbol file can pass the additional checks."""
1316 with multiprocessing.Manager() as mp_manager:
1317 sym_file = self.tempdir / "crash_reporter.sym"
1318 self._CreateSymbolFile(
1319 sym_file,
1320 func_lines=2000,
1321 public_lines=10,
1322 stack_lines=2000,
1323 line_number_lines=20000,
1324 )
1325 found_files = mp_manager.list()
1326 self.assertTrue(
1327 cros_generate_breakpad_symbols.ValidateSymbolFile(
1328 str(sym_file),
1329 "/build/board/sbin/crash_reporter",
1330 "/build/board",
1331 found_files,
1332 )
1333 )
1334 self.assertEqual(
1335 list(found_files),
1336 [cros_generate_breakpad_symbols.ExpectedFiles.CRASH_REPORTER],
1337 )
1338
1339 def testInvalidCrashReporterSymbolFile(self):
1340 """Test that a crash_reporter symbol file is held to higher standards"""
1341
1342 class CrashReporterSymbolFileTest:
1343 """Defines the subtest for an invalid crash_reporter symbol file."""
1344
1345 def __init__(
1346 self,
1347 name,
1348 expected_error,
1349 func_lines=2000,
1350 stack_lines=2000,
1351 line_number_lines=20000,
1352 ):
1353 self.name = name
1354 self.expected_error = expected_error
1355 self.func_lines = func_lines
1356 self.stack_lines = stack_lines
1357 self.line_number_lines = line_number_lines
1358
1359 CRASH_REPORTER_SYMBOL_TESTS = [
1360 CrashReporterSymbolFileTest(
1361 name="Insufficient FUNC records",
1362 func_lines=500,
1363 expected_error="crash_reporter should have at least 1000 FUNC "
1364 "records, found 500",
1365 ),
1366 CrashReporterSymbolFileTest(
1367 name="Insufficient STACK records",
1368 stack_lines=100,
1369 expected_error="crash_reporter should have at least 1000 STACK "
1370 "records, found 100",
1371 ),
1372 CrashReporterSymbolFileTest(
1373 name="Insufficient line number records",
1374 line_number_lines=2000,
1375 expected_error="crash_reporter should have at least 10,000 "
1376 "line number records, found 2000",
1377 ),
1378 ]
1379 for test in CRASH_REPORTER_SYMBOL_TESTS:
1380 with self.subTest(
1381 name=test.name
1382 ), multiprocessing.Manager() as mp_manager:
1383 sym_file = self.tempdir / "crash_reporter.sym"
1384 self._CreateSymbolFile(
1385 sym_file,
1386 func_lines=test.func_lines,
1387 stack_lines=test.stack_lines,
1388 line_number_lines=test.line_number_lines,
1389 )
1390 found_files = mp_manager.list()
1391 with self.assertLogs(level=logging.WARNING) as cm:
1392 self.assertFalse(
1393 cros_generate_breakpad_symbols.ValidateSymbolFile(
1394 str(sym_file),
1395 "/build/board/sbin/crash_reporter",
1396 "/build/board",
1397 found_files,
1398 )
1399 )
1400 self.assertIn(test.expected_error, cm.output[0])
1401 self.assertEqual(len(cm.output), 1)
1402
1403 def testValidLibMetricsSymbolFile(self):
1404 """Test a libmetrics.so symbol file can pass the additional checks."""
1405 with multiprocessing.Manager() as mp_manager:
1406 sym_file = self.tempdir / "libmetrics.so.sym"
1407 self._CreateSymbolFile(
1408 sym_file,
1409 func_lines=200,
1410 public_lines=2,
1411 stack_lines=2000,
1412 line_number_lines=10000,
1413 )
1414 found_files = mp_manager.list()
1415 self.assertTrue(
1416 cros_generate_breakpad_symbols.ValidateSymbolFile(
1417 str(sym_file),
1418 "/build/board/usr/lib64/libmetrics.so",
1419 "/build/board",
1420 found_files,
1421 )
1422 )
1423 self.assertEqual(
1424 list(found_files),
1425 [cros_generate_breakpad_symbols.ExpectedFiles.LIBMETRICS],
1426 )
1427
1428 def testInvalidLibMetricsSymbolFile(self):
1429 """Test that a libmetrics.so symbol file is held to higher standards."""
1430
1431 class LibMetricsSymbolFileTest:
1432 """Defines the subtest for an invalid libmetrics.so symbol file."""
1433
1434 def __init__(
1435 self,
1436 name,
1437 expected_error,
1438 func_lines=200,
1439 public_lines=2,
1440 stack_lines=2000,
1441 line_number_lines=10000,
1442 ):
1443 self.name = name
1444 self.expected_error = expected_error
1445 self.func_lines = func_lines
1446 self.public_lines = public_lines
1447 self.stack_lines = stack_lines
1448 self.line_number_lines = line_number_lines
1449
1450 LIBMETRICS_SYMBOL_TESTS = [
1451 LibMetricsSymbolFileTest(
1452 name="Insufficient FUNC records",
1453 func_lines=10,
1454 expected_error="libmetrics should have at least 100 FUNC "
1455 "records, found 10",
1456 ),
1457 LibMetricsSymbolFileTest(
1458 name="Insufficient PUBLIC records",
1459 public_lines=0,
1460 expected_error="libmetrics should have at least 1 PUBLIC "
1461 "record, found 0",
1462 ),
1463 LibMetricsSymbolFileTest(
1464 name="Insufficient STACK records",
1465 stack_lines=500,
1466 expected_error="libmetrics should have at least 1000 STACK "
1467 "records, found 500",
1468 ),
1469 LibMetricsSymbolFileTest(
1470 name="Insufficient line number records",
1471 line_number_lines=2000,
1472 expected_error="libmetrics should have at least 5000 "
1473 "line number records, found 2000",
1474 ),
1475 ]
1476 for test in LIBMETRICS_SYMBOL_TESTS:
1477 with self.subTest(
1478 name=test.name
1479 ), multiprocessing.Manager() as mp_manager:
1480 sym_file = self.tempdir / "libmetrics.so.sym"
1481 self._CreateSymbolFile(
1482 sym_file,
1483 func_lines=test.func_lines,
1484 public_lines=test.public_lines,
1485 stack_lines=test.stack_lines,
1486 line_number_lines=test.line_number_lines,
1487 )
1488 found_files = mp_manager.list()
1489 with self.assertLogs(level=logging.WARNING) as cm:
1490 self.assertFalse(
1491 cros_generate_breakpad_symbols.ValidateSymbolFile(
1492 str(sym_file),
1493 "/build/board/usr/lib64/libmetrics.so",
1494 "/build/board",
1495 found_files,
1496 )
1497 )
1498 self.assertIn(test.expected_error, cm.output[0])
1499 self.assertEqual(len(cm.output), 1)
1500
1501
Mike Frysinger69cb41d2013-08-11 20:08:19 -04001502class UtilsTestDir(cros_test_lib.TempDirTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -06001503 """Tests ReadSymsHeader."""
Mike Frysinger69cb41d2013-08-11 20:08:19 -04001504
Alex Klein1699fab2022-09-08 08:46:06 -06001505 def testReadSymsHeaderGoodFile(self):
1506 """Make sure ReadSymsHeader can parse sym files"""
1507 sym_file = os.path.join(self.tempdir, "sym")
1508 osutils.WriteFile(sym_file, "MODULE Linux x86 s0m31D chrooome")
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -08001509 result = cros_generate_breakpad_symbols.ReadSymsHeader(
1510 sym_file, "unused_elfname"
1511 )
Alex Klein1699fab2022-09-08 08:46:06 -06001512 self.assertEqual(result.cpu, "x86")
1513 self.assertEqual(result.id, "s0m31D")
1514 self.assertEqual(result.name, "chrooome")
1515 self.assertEqual(result.os, "Linux")
Mike Frysinger69cb41d2013-08-11 20:08:19 -04001516
1517
1518class UtilsTest(cros_test_lib.TestCase):
Alex Klein1699fab2022-09-08 08:46:06 -06001519 """Tests ReadSymsHeader."""
Mike Frysinger69cb41d2013-08-11 20:08:19 -04001520
Alex Klein1699fab2022-09-08 08:46:06 -06001521 def testReadSymsHeaderGoodBuffer(self):
1522 """Make sure ReadSymsHeader can parse sym file handles"""
1523 result = cros_generate_breakpad_symbols.ReadSymsHeader(
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -08001524 io.BytesIO(b"MODULE Linux arm MY-ID-HERE blkid"), "unused_elfname"
Alex Klein1699fab2022-09-08 08:46:06 -06001525 )
1526 self.assertEqual(result.cpu, "arm")
1527 self.assertEqual(result.id, "MY-ID-HERE")
1528 self.assertEqual(result.name, "blkid")
1529 self.assertEqual(result.os, "Linux")
Mike Frysinger69cb41d2013-08-11 20:08:19 -04001530
Alex Klein1699fab2022-09-08 08:46:06 -06001531 def testReadSymsHeaderBadd(self):
1532 """Make sure ReadSymsHeader throws on bad sym files"""
1533 self.assertRaises(
1534 ValueError,
1535 cros_generate_breakpad_symbols.ReadSymsHeader,
1536 io.BytesIO(b"asdf"),
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -08001537 "unused_elfname",
Alex Klein1699fab2022-09-08 08:46:06 -06001538 )
Mike Frysinger69cb41d2013-08-11 20:08:19 -04001539
Alex Klein1699fab2022-09-08 08:46:06 -06001540 def testBreakpadDir(self):
1541 """Make sure board->breakpad path expansion works"""
1542 expected = "/build/blah/usr/lib/debug/breakpad"
1543 result = cros_generate_breakpad_symbols.FindBreakpadDir("blah")
1544 self.assertEqual(expected, result)
Mike Frysinger69cb41d2013-08-11 20:08:19 -04001545
Alex Klein1699fab2022-09-08 08:46:06 -06001546 def testDebugDir(self):
1547 """Make sure board->debug path expansion works"""
1548 expected = "/build/blah/usr/lib/debug"
1549 result = cros_generate_breakpad_symbols.FindDebugDir("blah")
1550 self.assertEqual(expected, result)