blob: 0ce9af71f4428645b08c037b955abd5369a114d7 [file] [log] [blame]
# Copyright 2013 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Test cros_generate_breakpad_symbols."""
import ctypes
import io
import logging
import multiprocessing
import os
import pathlib
from unittest import mock
from chromite.lib import cros_test_lib
from chromite.lib import osutils
from chromite.lib import parallel_unittest
from chromite.lib import partial_mock
from chromite.scripts import cros_generate_breakpad_symbols
class FindDebugDirMock(partial_mock.PartialMock):
"""Mock out the DebugDir helper so we can point it to a tempdir."""
TARGET = "chromite.scripts.cros_generate_breakpad_symbols"
ATTRS = ("FindDebugDir",)
DEFAULT_ATTR = "FindDebugDir"
def __init__(self, path, *args, **kwargs) -> None:
self.path = path
super().__init__(*args, **kwargs)
# pylint: disable=unused-argument
def FindDebugDir(self, _board, sysroot=None):
return self.path
class IsSharedLibraryTest(cros_test_lib.TestCase):
"""Test IsSharedLibrary"""
def testSharedLibaries(self) -> None:
"""Verify that shared libraries return truthy"""
shared_libraries = [
"lib/libcontainer.so",
"lib64/libnss_db.so.2",
"usr/lib/libboost_type_erasure.so.1.81.0",
"usr/lib/v4l1compat.so",
]
for shared_library in shared_libraries:
self.assertTrue(
cros_generate_breakpad_symbols.IsSharedLibrary(shared_library),
msg=f"expected {shared_library} to be a shared library",
)
def testExecutables(self) -> None:
"""Verify that executables return None"""
executables = [
"sbin/crash_reporter",
"usr/bin/pqso", # ends in so but not .so
"usr/bin/perl5.36.0", # ends in numbers but not .so
]
for executable in executables:
self.assertFalse(
cros_generate_breakpad_symbols.IsSharedLibrary(executable),
msg=f"expected {executable} to not be a shared library",
)
# This long decorator triggers a false positive in the docstring test.
# https://github.com/PyCQA/pylint/issues/3077
# pylint: disable=bad-docstring-quotes
@mock.patch(
"chromite.scripts.cros_generate_breakpad_symbols." "GenerateBreakpadSymbol"
)
class GenerateSymbolsTest(cros_test_lib.MockTempDirTestCase):
"""Test GenerateBreakpadSymbols."""
def setUp(self) -> None:
self.board = "monkey-board"
self.board_dir = os.path.join(self.tempdir, "build", self.board)
self.debug_dir = os.path.join(self.board_dir, "usr", "lib", "debug")
self.breakpad_dir = os.path.join(self.debug_dir, "breakpad")
# Generate a tree of files which we'll scan through.
elf_files = [
"bin/elf",
"iii/large-elf",
# Need some kernel modules (with & without matching .debug).
"lib/modules/3.10/module.ko",
"lib/modules/3.10/module-no-debug.ko",
# Need a file which has an ELF only, but not a .debug.
"usr/bin/elf-only",
"usr/sbin/elf",
]
debug_files = [
"bin/bad-file",
"bin/elf.debug",
"iii/large-elf.debug",
"boot/vmlinux.debug",
"lib/modules/3.10/module.ko.debug",
# Need a file which has a .debug only, but not an ELF.
"sbin/debug-only.debug",
"usr/sbin/elf.debug",
]
for f in [os.path.join(self.board_dir, x) for x in elf_files] + [
os.path.join(self.debug_dir, x) for x in debug_files
]:
osutils.Touch(f, makedirs=True)
# Set up random build dirs and symlinks.
buildid = os.path.join(self.debug_dir, ".build-id", "00")
osutils.SafeMakedirs(buildid)
os.symlink("/asdf", os.path.join(buildid, "foo"))
os.symlink("/bin/sh", os.path.join(buildid, "foo.debug"))
os.symlink("/bin/sh", os.path.join(self.debug_dir, "file.debug"))
osutils.WriteFile(
os.path.join(self.debug_dir, "iii", "large-elf.debug"),
"just some content",
)
self.StartPatcher(FindDebugDirMock(self.debug_dir))
def markAllFilesAsProcessed(self, gen_mock) -> None:
"""Sets mock to pretend it processed all the expected ELF files.
This avoids having GenerateBreakpadSymbols return an error because not
all expected files were processed.
"""
expected_found = list(cros_generate_breakpad_symbols.ALL_EXPECTED_FILES)
def _SetFound(*_args, **kwargs):
kwargs["found_files"].extend(expected_found)
gen_mock.side_effect = None
return 1
gen_mock.side_effect = _SetFound
def testNormal(self, gen_mock) -> None:
"""Verify all the files we expect to get generated do"""
with parallel_unittest.ParallelMock():
self.markAllFilesAsProcessed(gen_mock)
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
self.board, sysroot=self.board_dir
)
self.assertEqual(ret, 0)
self.assertEqual(gen_mock.call_count, 5)
# The largest ELF should be processed first.
call1 = (
os.path.join(self.board_dir, "iii/large-elf"),
os.path.join(self.debug_dir, "iii/large-elf.debug"),
)
self.assertEqual(gen_mock.call_args_list[0][0], call1)
# The other ELFs can be called in any order.
call2 = (
os.path.join(self.board_dir, "bin/elf"),
os.path.join(self.debug_dir, "bin/elf.debug"),
)
call3 = (
os.path.join(self.board_dir, "usr/sbin/elf"),
os.path.join(self.debug_dir, "usr/sbin/elf.debug"),
)
call4 = (
os.path.join(self.board_dir, "lib/modules/3.10/module.ko"),
os.path.join(
self.debug_dir, "lib/modules/3.10/module.ko.debug"
),
)
call5 = (
os.path.join(self.board_dir, "boot/vmlinux"),
os.path.join(self.debug_dir, "boot/vmlinux.debug"),
)
exp_calls = set((call2, call3, call4, call5))
actual_calls = set(
(
gen_mock.call_args_list[1][0],
gen_mock.call_args_list[2][0],
gen_mock.call_args_list[3][0],
gen_mock.call_args_list[4][0],
)
)
self.assertEqual(exp_calls, actual_calls)
def testFileList(self, gen_mock) -> None:
"""Verify that file_list restricts the symbols generated"""
with parallel_unittest.ParallelMock():
# Don't need markAllFilesAsProcessed since using file_list will
# skip the expected-files-processed check.
call1 = (
os.path.join(self.board_dir, "usr/sbin/elf"),
os.path.join(self.debug_dir, "usr/sbin/elf.debug"),
)
# Filter with elf path.
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
self.board,
sysroot=self.board_dir,
breakpad_dir=self.breakpad_dir,
file_list=[os.path.join(self.board_dir, "usr", "sbin", "elf")],
)
self.assertEqual(ret, 0)
self.assertEqual(gen_mock.call_count, 1)
self.assertEqual(gen_mock.call_args_list[0][0], call1)
# Filter with debug symbols file path.
gen_mock.reset_mock()
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
self.board,
sysroot=self.board_dir,
breakpad_dir=self.breakpad_dir,
file_list=[
os.path.join(self.debug_dir, "usr", "sbin", "elf.debug")
],
)
self.assertEqual(ret, 0)
self.assertEqual(gen_mock.call_count, 1)
self.assertEqual(gen_mock.call_args_list[0][0], call1)
def testGenLimit(self, gen_mock) -> None:
"""Verify generate_count arg works"""
with parallel_unittest.ParallelMock():
# Generate nothing!
# Don't need markAllFilesAsProcessed since using generate_count will
# skip the expected-files-processed check.
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
self.board,
sysroot=self.board_dir,
breakpad_dir=self.breakpad_dir,
generate_count=0,
)
self.assertEqual(ret, 0)
self.assertEqual(gen_mock.call_count, 0)
# Generate just one.
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
self.board,
sysroot=self.board_dir,
breakpad_dir=self.breakpad_dir,
generate_count=1,
)
self.assertEqual(ret, 0)
self.assertEqual(gen_mock.call_count, 1)
# The largest ELF should be processed first.
call1 = (
os.path.join(self.board_dir, "iii/large-elf"),
os.path.join(self.debug_dir, "iii/large-elf.debug"),
)
self.assertEqual(gen_mock.call_args_list[0][0], call1)
def testGenErrors(self, gen_mock) -> None:
"""Verify we handle errors from generation correctly"""
def _SetError(*_args, **kwargs):
kwargs["num_errors"].value += 1
return 1
gen_mock.side_effect = _SetError
with parallel_unittest.ParallelMock():
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
self.board, sysroot=self.board_dir
)
# Expect 5 errors from calls plus 1 from the
# expected-files-processed check.
self.assertEqual(ret, 6)
self.assertEqual(gen_mock.call_count, 5)
def testCleaningTrue(self, gen_mock) -> None:
"""Verify behavior of clean_breakpad=True"""
with parallel_unittest.ParallelMock():
self.markAllFilesAsProcessed(gen_mock)
# Dir does not exist, and then does.
self.assertNotExists(self.breakpad_dir)
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
self.board,
sysroot=self.board_dir,
generate_count=1,
clean_breakpad=True,
)
self.assertEqual(ret, 0)
self.assertEqual(gen_mock.call_count, 1)
self.assertExists(self.breakpad_dir)
# Dir exists before & after.
# File exists, but then doesn't.
stub_file = os.path.join(self.breakpad_dir, "fooooooooo")
osutils.Touch(stub_file)
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
self.board,
sysroot=self.board_dir,
generate_count=1,
clean_breakpad=True,
)
self.assertEqual(ret, 0)
self.assertEqual(gen_mock.call_count, 2)
self.assertNotExists(stub_file)
def testCleaningFalse(self, gen_mock) -> None:
"""Verify behavior of clean_breakpad=False"""
with parallel_unittest.ParallelMock():
self.markAllFilesAsProcessed(gen_mock)
# Dir does not exist, and then does.
self.assertNotExists(self.breakpad_dir)
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
self.board,
sysroot=self.board_dir,
generate_count=1,
clean_breakpad=False,
)
self.assertEqual(ret, 0)
self.assertEqual(gen_mock.call_count, 1)
self.assertExists(self.breakpad_dir)
# Dir exists before & after.
# File exists before & after.
stub_file = os.path.join(self.breakpad_dir, "fooooooooo")
osutils.Touch(stub_file)
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
self.board,
sysroot=self.board_dir,
generate_count=1,
clean_breakpad=False,
)
self.assertEqual(ret, 0)
self.assertEqual(gen_mock.call_count, 2)
self.assertExists(stub_file)
def testExclusionList(self, gen_mock) -> None:
"""Verify files in directories of the exclusion list are excluded"""
exclude_dirs = ["bin", "usr", "fake/dir/fake"]
with parallel_unittest.ParallelMock():
self.markAllFilesAsProcessed(gen_mock)
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
self.board, sysroot=self.board_dir, exclude_dirs=exclude_dirs
)
self.assertEqual(ret, 0)
self.assertEqual(gen_mock.call_count, 3)
def testExpectedFilesCompleteFailure(self, _) -> None:
"""Verify if no files are processed, all expected files give errors"""
with parallel_unittest.ParallelMock() and self.assertLogs(
level=logging.WARNING
) as cm:
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
self.board, sysroot=self.board_dir
)
self.assertEqual(ret, 1)
self.assertIn(
"Not all expected files were processed successfully",
"\n".join(cm.output),
)
for output in cm.output:
if (
"Not all expected files were processed successfully"
in output
):
# This is the line that lists all the files we didn't find.
for (
expected_file
) in cros_generate_breakpad_symbols.ExpectedFiles:
self.assertIn(expected_file.name, output)
def testExpectedFilesPartialFailure(self, gen_mock) -> None:
"""If some expected files are processed, the others give errors"""
expected_found = (
cros_generate_breakpad_symbols.ExpectedFiles.LIBC,
cros_generate_breakpad_symbols.ExpectedFiles.CRASH_REPORTER,
)
def _SetFound(*_args, **kwargs):
kwargs["found_files"].extend(expected_found)
gen_mock.side_effect = None
return 1
gen_mock.side_effect = _SetFound
with parallel_unittest.ParallelMock() and self.assertLogs(
level=logging.WARNING
) as cm:
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
self.board, sysroot=self.board_dir
)
self.assertEqual(ret, 1)
self.assertIn(
"Not all expected files were processed successfully",
"\n".join(cm.output),
)
for output in cm.output:
if (
"Not all expected files were processed successfully"
in output
):
# This is the line that lists all the files we didn't find.
for (
expected_file
) in cros_generate_breakpad_symbols.ExpectedFiles:
if expected_file in expected_found:
self.assertNotIn(expected_file.name, output)
else:
self.assertIn(expected_file.name, output)
def testExpectedFilesWithSomeIgnored(self, _) -> None:
"""If some expected files are ignored, they don't give errors"""
ignore_expected_files = [
cros_generate_breakpad_symbols.ExpectedFiles.ASH_CHROME,
cros_generate_breakpad_symbols.ExpectedFiles.LIBC,
]
with parallel_unittest.ParallelMock() and self.assertLogs(
level=logging.WARNING
) as cm:
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
self.board,
sysroot=self.board_dir,
ignore_expected_files=ignore_expected_files,
)
self.assertEqual(ret, 1)
self.assertIn(
"Not all expected files were processed successfully",
"\n".join(cm.output),
)
for output in cm.output:
if (
"Not all expected files were processed successfully"
in output
):
# This is the line that lists all the files we didn't find.
for (
expected_file
) in cros_generate_breakpad_symbols.ExpectedFiles:
if expected_file in ignore_expected_files:
self.assertNotIn(expected_file.name, output)
else:
self.assertIn(expected_file.name, output)
def testExpectedFilesWithAllIgnored(self, _) -> None:
"""If all expected files are ignored, there is no error"""
with parallel_unittest.ParallelMock() and self.assertLogs(
level=logging.WARNING
) as cm:
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
self.board,
sysroot=self.board_dir,
ignore_expected_files=list(
cros_generate_breakpad_symbols.ExpectedFiles
),
)
self.assertEqual(ret, 0)
self.assertNotIn(
"Not all expected files were processed successfully",
"\n".join(cm.output),
)
def testExpectedFilesWithSomeIgnoredAndSomeFound(self, gen_mock) -> None:
"""Some expected files are ignored, others processed => no error"""
expected_found = (
cros_generate_breakpad_symbols.ExpectedFiles.LIBC,
cros_generate_breakpad_symbols.ExpectedFiles.CRASH_REPORTER,
)
def _SetFound(*_args, **kwargs):
kwargs["found_files"].extend(expected_found)
gen_mock.side_effect = None
return 1
gen_mock.side_effect = _SetFound
with parallel_unittest.ParallelMock() and self.assertLogs(
level=logging.WARNING
) as cm:
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
self.board,
sysroot=self.board_dir,
ignore_expected_files=[
cros_generate_breakpad_symbols.ExpectedFiles.ASH_CHROME,
cros_generate_breakpad_symbols.ExpectedFiles.LIBMETRICS,
],
)
self.assertEqual(ret, 0)
self.assertNotIn(
"Not all expected files were processed successfully",
"\n".join(cm.output),
)
class GenerateSymbolTest(cros_test_lib.RunCommandTempDirTestCase):
"""Test GenerateBreakpadSymbol."""
_DUMP_SYMS_BASE_CMD = ["dump_syms", "-v", "-d", "-m"]
def setUp(self) -> None:
self.elf_file = os.path.join(self.tempdir, "elf")
osutils.Touch(self.elf_file)
self.debug_dir = os.path.join(self.tempdir, "debug")
self.debug_file = os.path.join(self.debug_dir, "elf.debug")
osutils.Touch(self.debug_file, makedirs=True)
# Not needed as the code itself should create it as needed.
self.breakpad_dir = os.path.join(self.debug_dir, "breakpad")
self.FILE_OUT = (
f"{self.elf_file}: ELF 64-bit LSB pie executable, x86-64, "
"version 1 (SYSV), dynamically linked, interpreter "
"/lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, "
"BuildID[sha1]=cf9a21fa6b14bfb2dfcb76effd713c4536014d95, stripped"
)
# A symbol file which would pass validation.
MINIMAL_SYMBOL_FILE = (
"MODULE OS CPU ID NAME\n"
"PUBLIC f10 0 func\n"
"STACK CFI INIT f10 22 .cfa: $rsp 8 + .ra: .cfa -8 + ^\n"
)
self.rc.SetDefaultCmdResult(stdout=MINIMAL_SYMBOL_FILE)
self.rc.AddCmdResult(
["/usr/bin/file", self.elf_file], stdout=self.FILE_OUT
)
self.assertCommandContains = self.rc.assertCommandContains
self.sym_file = os.path.join(self.breakpad_dir, "NAME/ID/NAME.sym")
self.StartPatcher(FindDebugDirMock(self.debug_dir))
def assertCommandArgs(self, i, args) -> None:
"""Helper for looking at the args of the |i|th call"""
self.assertEqual(self.rc.call_args_list[i][0][0], args)
def testNormal(self) -> None:
"""Normal run -- given an ELF and a debug file"""
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
self.elf_file,
self.debug_file,
self.breakpad_dir,
)
self.assertEqual(ret, self.sym_file)
self.assertEqual(self.rc.call_count, 2)
self.assertCommandArgs(
1, self._DUMP_SYMS_BASE_CMD + [self.elf_file, self.debug_dir]
)
self.assertExists(self.sym_file)
def testNormalNoCfi(self) -> None:
"""Normal run w/out CFI"""
# Make sure the num_errors flag works too.
num_errors = ctypes.c_int(0)
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
self.elf_file,
self.debug_file,
breakpad_dir=self.breakpad_dir,
strip_cfi=True,
num_errors=num_errors,
)
self.assertEqual(ret, self.sym_file)
self.assertEqual(num_errors.value, 0)
self.assertCommandArgs(
1, self._DUMP_SYMS_BASE_CMD + ["-c", self.elf_file, self.debug_dir]
)
self.assertEqual(self.rc.call_count, 2)
self.assertExists(self.sym_file)
def testNormalElfOnly(self) -> None:
"""Normal run with just an ELF will fail"""
num_errors = ctypes.c_int(0)
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
self.elf_file,
breakpad_dir=self.breakpad_dir,
num_errors=num_errors,
)
self.assertEqual(ret, 1)
self.assertEqual(num_errors.value, 1)
self.assertNotExists(self.sym_file)
def testNormalSudo(self) -> None:
"""Normal run where ELF is readable only by root"""
with mock.patch.object(os, "access") as mock_access:
mock_access.return_value = False
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
self.elf_file, self.debug_file, breakpad_dir=self.breakpad_dir
)
self.assertEqual(ret, self.sym_file)
self.assertCommandArgs(
1,
["sudo", "--"]
+ self._DUMP_SYMS_BASE_CMD
+ [self.elf_file, self.debug_dir],
)
def testDumpSymsFail(self) -> None:
"""The call to dump_syms failed"""
self.rc.AddCmdResult(
self._DUMP_SYMS_BASE_CMD + [self.elf_file, self.debug_dir],
returncode=1,
)
num_errors = ctypes.c_int(0)
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
self.elf_file,
self.debug_file,
breakpad_dir=self.breakpad_dir,
num_errors=num_errors,
)
self.assertEqual(ret, 1)
self.assertEqual(num_errors.value, 1)
self.assertEqual(self.rc.call_count, 2)
self.assertCommandArgs(
1, self._DUMP_SYMS_BASE_CMD + [self.elf_file, self.debug_dir]
)
self.assertNotExists(self.sym_file)
def testValidationFail(self) -> None:
"""If symbol file validation fails, return an error"""
BAD_SYMBOL_FILE = "MODULE OS CPU ID NAME\n"
self.rc.SetDefaultCmdResult(stdout=BAD_SYMBOL_FILE)
num_errors = ctypes.c_int(0)
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
self.elf_file,
self.debug_file,
breakpad_dir=self.breakpad_dir,
strip_cfi=True,
num_errors=num_errors,
)
self.assertEqual(ret, 1)
self.assertEqual(num_errors.value, 1)
self.assertNotExists(self.sym_file)
def testValidationRaises(self) -> None:
"""If symbol file validation raises an error, return an error."""
BAD_SYMBOL_FILE = (
"MODULE Linux x86 D3096ED481217FD4C16B29CD9BC208BA0 elf\n"
"JUNK LINE IS BAD LINE\n"
)
self.rc.SetDefaultCmdResult(stdout=BAD_SYMBOL_FILE)
num_errors = ctypes.c_int(0)
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
self.elf_file,
self.debug_file,
breakpad_dir=self.breakpad_dir,
strip_cfi=True,
num_errors=num_errors,
)
self.assertEqual(ret, 1)
self.assertEqual(num_errors.value, 1)
self.assertNotExists(self.sym_file)
def testForceBasicFallback(self) -> None:
"""Running with force_basic_fallback
Test force_basic_fallback goes straight to _DumpAllowingBasicFallback().
"""
self.rc.AddCmdResult(
self._DUMP_SYMS_BASE_CMD + [self.elf_file, self.debug_dir],
returncode=1,
)
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
self.elf_file,
self.debug_file,
breakpad_dir=self.breakpad_dir,
force_basic_fallback=True,
)
self.assertEqual(ret, self.sym_file)
self.assertEqual(self.rc.call_count, 2)
self.assertCommandArgs(
0, self._DUMP_SYMS_BASE_CMD + [self.elf_file, self.debug_dir]
)
self.assertCommandArgs(
1,
self._DUMP_SYMS_BASE_CMD
+ ["-c", "-r", self.elf_file, self.debug_dir],
)
self.assertExists(self.sym_file)
def testForceBasicFallbackElfOnly(self) -> None:
"""Running with force_basic_fallback run given just an ELF"""
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
self.elf_file,
breakpad_dir=self.breakpad_dir,
force_basic_fallback=True,
)
self.assertEqual(ret, self.sym_file)
self.assertEqual(self.rc.call_count, 1)
self.assertCommandArgs(0, self._DUMP_SYMS_BASE_CMD + [self.elf_file])
self.assertExists(self.sym_file)
def testForceBasicFallbackLargeDebugFail(self) -> None:
"""In fallback mode, running w/large .debug failed, but retry worked"""
self.rc.AddCmdResult(
self._DUMP_SYMS_BASE_CMD + [self.elf_file, self.debug_dir],
returncode=1,
)
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
self.elf_file,
self.debug_file,
breakpad_dir=self.breakpad_dir,
force_basic_fallback=True,
)
self.assertEqual(ret, self.sym_file)
self.assertEqual(self.rc.call_count, 2)
self.assertCommandArgs(
0, self._DUMP_SYMS_BASE_CMD + [self.elf_file, self.debug_dir]
)
self.assertCommandArgs(
1,
self._DUMP_SYMS_BASE_CMD
+ ["-c", "-r", self.elf_file, self.debug_dir],
)
self.assertExists(self.sym_file)
def testForceBasicFallbackDebugFail(self) -> None:
"""In fallback mode, running w/.debug always fails, but works without"""
self.rc.AddCmdResult(
self._DUMP_SYMS_BASE_CMD + [self.elf_file, self.debug_dir],
returncode=1,
)
self.rc.AddCmdResult(
self._DUMP_SYMS_BASE_CMD
+ ["-c", "-r", self.elf_file, self.debug_dir],
returncode=1,
)
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
self.elf_file,
self.debug_file,
breakpad_dir=self.breakpad_dir,
force_basic_fallback=True,
)
self.assertEqual(ret, self.sym_file)
self.assertEqual(self.rc.call_count, 3)
self.assertCommandArgs(
0, self._DUMP_SYMS_BASE_CMD + [self.elf_file, self.debug_dir]
)
self.assertCommandArgs(
1,
self._DUMP_SYMS_BASE_CMD
+ ["-c", "-r", self.elf_file, self.debug_dir],
)
self.assertCommandArgs(2, self._DUMP_SYMS_BASE_CMD + [self.elf_file])
self.assertExists(self.sym_file)
def testForceBasicFallbackCompleteFail(self) -> None:
"""In fallback mode, if dump_syms always fails, still an error"""
self.rc.SetDefaultCmdResult(returncode=1)
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
self.elf_file,
breakpad_dir=self.breakpad_dir,
force_basic_fallback=True,
)
self.assertEqual(ret, 1)
# Make sure the num_errors flag works too.
num_errors = ctypes.c_int(0)
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
self.elf_file, breakpad_dir=self.breakpad_dir, num_errors=num_errors
)
self.assertEqual(ret, 1)
self.assertEqual(num_errors.value, 1)
def testKernelObjects(self) -> None:
"""Kernel object files should call _DumpAllowingBasicFallback()"""
ko_file = os.path.join(self.tempdir, "elf.ko")
osutils.Touch(ko_file)
self.rc.AddCmdResult(
self._DUMP_SYMS_BASE_CMD + [ko_file, self.debug_dir],
returncode=1,
)
self.rc.AddCmdResult(
self._DUMP_SYMS_BASE_CMD + ["-c", "-r", ko_file, self.debug_dir],
returncode=1,
)
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
ko_file, self.debug_file, self.breakpad_dir
)
self.assertEqual(ret, self.sym_file)
self.assertEqual(self.rc.call_count, 3)
self.assertCommandArgs(
0, self._DUMP_SYMS_BASE_CMD + [ko_file, self.debug_dir]
)
self.assertCommandArgs(
1,
self._DUMP_SYMS_BASE_CMD + ["-c", "-r", ko_file, self.debug_dir],
)
self.assertCommandArgs(2, self._DUMP_SYMS_BASE_CMD + [ko_file])
self.assertExists(self.sym_file)
def testGoBinary(self) -> None:
"""Go binaries should call _DumpAllowingBasicFallback()
Also tests that dump_syms failing with 'file contains no debugging
information' does not fail the script.
"""
go_binary = os.path.join(self.tempdir, "goprogram")
osutils.Touch(go_binary)
go_debug_file = os.path.join(self.debug_dir, "goprogram.debug")
osutils.Touch(go_debug_file, makedirs=True)
FILE_OUT_GO = go_binary + (
": ELF 64-bit LSB executable, x86-64, "
"version 1 (SYSV), statically linked, "
"Go BuildID=KKXVlL66E8Qmngr4qll9/5kOKGZw9I7TmNhoqKLqq/SiYVJam6w5Fo"
"39B3BtDo/ba8_ceezZ-3R4qEv6_-K, not stripped"
)
self.rc.AddCmdResult(["/usr/bin/file", go_binary], stdout=FILE_OUT_GO)
self.rc.AddCmdResult(
self._DUMP_SYMS_BASE_CMD + [go_binary, self.debug_dir],
returncode=1,
)
self.rc.AddCmdResult(
self._DUMP_SYMS_BASE_CMD + ["-c", "-r", go_binary, self.debug_dir],
returncode=1,
)
self.rc.AddCmdResult(
self._DUMP_SYMS_BASE_CMD + [go_binary],
returncode=1,
stderr=(
f"{go_binary}: file contains no debugging information "
'(no ".stab" or ".debug_info" sections)'
),
)
num_errors = ctypes.c_int(0)
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
go_binary, go_debug_file, self.breakpad_dir
)
self.assertEqual(ret, 0)
self.assertEqual(self.rc.call_count, 4)
self.assertCommandArgs(0, ["/usr/bin/file", go_binary])
self.assertCommandArgs(
1, self._DUMP_SYMS_BASE_CMD + [go_binary, self.debug_dir]
)
self.assertCommandArgs(
2,
self._DUMP_SYMS_BASE_CMD + ["-c", "-r", go_binary, self.debug_dir],
)
self.assertCommandArgs(3, self._DUMP_SYMS_BASE_CMD + [go_binary])
self.assertNotExists(self.sym_file)
self.assertEqual(num_errors.value, 0)
def _testBinaryIsInLocalFallback(self, directory, filename) -> None:
binary = os.path.join(self.tempdir, directory, filename)
osutils.Touch(binary, makedirs=True)
debug_dir = os.path.join(self.debug_dir, directory)
debug_file = os.path.join(debug_dir, f"{filename}.debug")
osutils.Touch(debug_file, makedirs=True)
self.rc.AddCmdResult(["/usr/bin/file", binary], stdout=self.FILE_OUT)
self.rc.AddCmdResult(
self._DUMP_SYMS_BASE_CMD + [binary, debug_dir], returncode=1
)
self.rc.AddCmdResult(
self._DUMP_SYMS_BASE_CMD + ["-c", "-r", binary, debug_dir],
returncode=1,
)
self.rc.AddCmdResult(
self._DUMP_SYMS_BASE_CMD + [binary],
returncode=1,
stderr=(
f"{binary}: file contains no debugging information "
'(no ".stab" or ".debug_info" sections)'
),
)
num_errors = ctypes.c_int(0)
ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
binary, debug_file, self.breakpad_dir, sysroot=self.tempdir
)
self.assertEqual(ret, 0)
self.assertEqual(self.rc.call_count, 4)
self.assertCommandArgs(0, ["/usr/bin/file", binary])
self.assertCommandArgs(
1, self._DUMP_SYMS_BASE_CMD + [binary, debug_dir]
)
self.assertCommandArgs(
2, self._DUMP_SYMS_BASE_CMD + ["-c", "-r", binary, debug_dir]
)
self.assertCommandArgs(3, self._DUMP_SYMS_BASE_CMD + [binary])
self.assertNotExists(self.sym_file)
self.assertEqual(num_errors.value, 0)
def testAllowlist(self) -> None:
"""Binaries in the allowlist should call _DumpAllowingBasicFallback()"""
self._testBinaryIsInLocalFallback("usr/bin", "goldctl")
def testUsrLocalSkip(self) -> None:
"""Binaries in /usr/local should call _DumpAllowingBasicFallback()"""
self._testBinaryIsInLocalFallback("usr/local", "minidump_stackwalk")
class ValidateSymbolFileTest(cros_test_lib.TempDirTestCase):
"""Tests ValidateSymbolFile"""
def _GetTestdataFile(self, filename: str) -> str:
"""Gets the path to a file in the testdata directory.
Args:
filename: The base filename of the file.
Returns:
A string with the complete path to the file.
"""
return os.path.join(os.path.dirname(__file__), "testdata", filename)
def testValidSymbolFiles(self) -> None:
"""Make sure ValidateSymbolFile passes on valid files"""
# All files are in the testdata/ subdirectory.
VALID_SYMBOL_FILES = [
# A "normal" symbol file from an executable.
"basic.sym",
# A "normal" symbol file from a shared library.
"basic_lib.sym",
# A symbol file with PUBLIC records but no FUNC records.
"public_only.sym",
# A symbol file with FUNC records but no PUBLIC records.
"func_only.sym",
# A symbol file with at least one of every line type.
"all_line_types.sym",
]
for file in VALID_SYMBOL_FILES:
with self.subTest(
file=file
), multiprocessing.Manager() as mp_manager:
found_files = mp_manager.list()
self.assertTrue(
cros_generate_breakpad_symbols.ValidateSymbolFile(
self._GetTestdataFile(file),
"/build/board/bin/foo",
"/build/board",
found_files,
)
)
self.assertFalse(found_files)
def testInvalidSymbolFiles(self) -> None:
"""Make sure ValidateSymbolFile fails on invalid files.
This test only covers cases that return false, not cases that raise
exceptions.
"""
class InvalidSymbolFile:
"""The name of an invalid symbol file + the expected error msg."""
def __init__(self, filename, expected_errors) -> None:
self.filename = filename
self.expected_errors = expected_errors
INVALID_SYMBOL_FILES = [
InvalidSymbolFile(
"bad_no_func_or_public.sym",
[
"WARNING:root:/build/board/bin/foo: "
"Symbol file has no FUNC or PUBLIC records"
],
),
InvalidSymbolFile(
"bad_no_stack.sym",
[
"WARNING:root:/build/board/bin/foo: "
"Symbol file has no STACK records"
],
),
InvalidSymbolFile(
"bad_no_module.sym",
[
"WARNING:root:/build/board/bin/foo: "
"Symbol file has 0 MODULE lines"
],
),
InvalidSymbolFile(
"bad_two_modules.sym",
[
"WARNING:root:/build/board/bin/foo: "
"Symbol file has 2 MODULE lines"
],
),
InvalidSymbolFile(
"bad_func_no_line_numbers.sym",
[
"WARNING:root:/build/board/bin/foo: "
"Symbol file has FUNC records but no line numbers"
],
),
InvalidSymbolFile(
"bad_line_numbers_no_file.sym",
[
"WARNING:root:/build/board/bin/foo: "
"Symbol file has line number records but no FILE records"
],
),
InvalidSymbolFile(
"bad_inline_no_files.sym",
[
"WARNING:root:/build/board/bin/foo: "
"Symbol file has INLINE records but no FILE records"
],
),
InvalidSymbolFile(
"bad_inline_no_origins.sym",
[
"WARNING:root:/build/board/bin/foo: "
"Symbol file has INLINE records but no INLINE_ORIGIN "
"records"
],
),
InvalidSymbolFile(
"blank.sym",
[
"WARNING:root:/build/board/bin/foo: "
"Symbol file has no STACK records",
"WARNING:root:/build/board/bin/foo: "
"Symbol file has 0 MODULE lines",
"WARNING:root:/build/board/bin/foo: "
"Symbol file has no FUNC or PUBLIC records",
],
),
]
for file in INVALID_SYMBOL_FILES:
with self.subTest(
file=file.filename
), multiprocessing.Manager() as mp_manager:
found_files = mp_manager.list()
with self.assertLogs(level=logging.WARNING) as cm:
self.assertFalse(
cros_generate_breakpad_symbols.ValidateSymbolFile(
self._GetTestdataFile(file.filename),
"/build/board/bin/foo",
"/build/board",
found_files,
)
)
self.assertEqual(file.expected_errors, cm.output)
self.assertFalse(found_files)
def testInvalidSymbolFilesWhichRaise(self) -> None:
"""Test ValidateSymbolFile raise exceptions on certain files"""
class InvalidSymbolFile:
"""The invalid symbol file + the expected exception message"""
def __init__(self, filename, expected_exception_regex) -> None:
self.filename = filename
self.expected_exception_regex = expected_exception_regex
INVALID_SYMBOL_FILES = [
InvalidSymbolFile(
"bad_unknown_line_type.sym",
r"symbol file has unknown line type UNKNOWN "
r"\(line='UNKNOWN line type\n'\)",
),
InvalidSymbolFile(
"bad_blank_line.sym",
"symbol file has unexpected blank line",
),
InvalidSymbolFile(
"bad_short_func.sym",
r"symbol file has FUNC line with 2 words "
r"\(expected 5 or more\) \(line='FUNC fb0\n'\)",
),
InvalidSymbolFile(
"bad_short_line_number.sym",
r"symbol file has line number line with 3 words "
r"\(expected 4 - 4\) \(line='fb0 106 0\n'\)",
),
InvalidSymbolFile(
"bad_long_line_number.sym",
r"symbol file has line number line with 5 words "
r"\(expected 4 - 4\) \(line='c184 7 59 4 8\n'\)",
),
]
for file in INVALID_SYMBOL_FILES:
with self.subTest(
file=file.filename
), multiprocessing.Manager() as mp_manager:
found_files = mp_manager.list()
self.assertRaisesRegex(
ValueError,
file.expected_exception_regex,
cros_generate_breakpad_symbols.ValidateSymbolFile,
self._GetTestdataFile(file.filename),
"/build/board/bin/foo",
"/build/board",
found_files,
)
def testAllowlist(self) -> None:
"""Test that ELFs on the allowlist are allowed to pass."""
with multiprocessing.Manager() as mp_manager:
found_files = mp_manager.list()
self.assertTrue(
cros_generate_breakpad_symbols.ValidateSymbolFile(
self._GetTestdataFile("bad_no_module.sym"),
"/build/board/opt/google/chrome/nacl_helper_bootstrap",
"/build/board",
found_files,
)
)
self.assertFalse(found_files)
def testAllowlistRegex(self) -> None:
"""Test that ELFs on the regex-based allowlist are allowed to pass."""
with multiprocessing.Manager() as mp_manager:
found_files = mp_manager.list()
self.assertTrue(
cros_generate_breakpad_symbols.ValidateSymbolFile(
self._GetTestdataFile("bad_no_module.sym"),
"/build/board/lib64/libnss_dns.so.2",
"/build/board",
found_files,
)
)
self.assertFalse(found_files)
def testSharedLibrariesSkipStackTest(self) -> None:
"""Test that shared libraries can pass validation with no STACK."""
with multiprocessing.Manager() as mp_manager:
found_files = mp_manager.list()
self.assertTrue(
cros_generate_breakpad_symbols.ValidateSymbolFile(
self._GetTestdataFile("bad_no_stack.sym"),
"/build/board/lib64/libiw.so.30",
"/build/board",
found_files,
)
)
self.assertFalse(found_files)
def _CreateSymbolFile(
self,
sym_file: pathlib.Path,
func_lines: int = 0,
public_lines: int = 0,
stack_lines: int = 0,
line_number_lines: int = 0,
) -> None:
"""Creates a symbol file.
Creates a symbol file with the given number of lines (and enough other
lines to pass validation) in the temp directory.
To pass validation, chrome.sym files must be huge; create them
programmatically during the test instead of checking in a real 800MB+
chrome symbol file.
"""
with sym_file.open(mode="w", encoding="utf-8") as f:
f.write("MODULE OS CPU ID NAME\n")
f.write("FILE 0 /path/to/source.cc\n")
for func in range(0, func_lines):
f.write(f"FUNC {func} 1 0 function{func}\n")
for public in range(0, public_lines):
f.write(f"PUBLIC {public} 0 Public{public}\n")
for line in range(0, line_number_lines):
f.write(f"{line} 1 {line} 0\n")
for stack in range(0, stack_lines):
f.write(f"STACK CFI {stack} .cfa: $esp {stack} +\n")
def testValidChromeSymbolFile(self) -> None:
"""Test that a chrome symbol file can pass the additional checks"""
sym_file = self.tempdir / "chrome.sym"
self._CreateSymbolFile(
sym_file,
func_lines=100000,
public_lines=10,
stack_lines=1000000,
line_number_lines=1000000,
)
with multiprocessing.Manager() as mp_manager:
found_files = mp_manager.list()
self.assertTrue(
cros_generate_breakpad_symbols.ValidateSymbolFile(
str(sym_file),
"/build/board/opt/google/chrome/chrome",
"/build/board",
found_files,
)
)
self.assertEqual(
list(found_files),
[cros_generate_breakpad_symbols.ExpectedFiles.ASH_CHROME],
)
def testInvalidChromeSymbolFile(self) -> None:
"""Test that a chrome symbol file is held to higher standards."""
class ChromeSymbolFileTest:
"""Defines the subtest for an invalid Chrome symbol file."""
def __init__(
self,
name,
expected_error,
func_lines=100000,
stack_lines=1000000,
line_number_lines=1000000,
) -> None:
self.name = name
self.expected_error = expected_error
self.func_lines = func_lines
self.stack_lines = stack_lines
self.line_number_lines = line_number_lines
CHROME_SYMBOL_TESTS = [
ChromeSymbolFileTest(
name="Insufficient FUNC records",
func_lines=10000,
expected_error="chrome should have at least 100,000 FUNC "
"records, found 10000",
),
ChromeSymbolFileTest(
name="Insufficient STACK records",
stack_lines=100000,
expected_error="chrome should have at least 1,000,000 STACK "
"records, found 100000",
),
ChromeSymbolFileTest(
name="Insufficient line number records",
line_number_lines=100000,
expected_error="chrome should have at least 1,000,000 "
"line number records, found 100000",
),
]
for test in CHROME_SYMBOL_TESTS:
with self.subTest(
name=test.name
), multiprocessing.Manager() as mp_manager:
sym_file = self.tempdir / "chrome.sym"
self._CreateSymbolFile(
sym_file,
func_lines=test.func_lines,
public_lines=10,
stack_lines=test.stack_lines,
line_number_lines=test.line_number_lines,
)
found_files = mp_manager.list()
with self.assertLogs(level=logging.WARNING) as cm:
self.assertFalse(
cros_generate_breakpad_symbols.ValidateSymbolFile(
str(sym_file),
"/build/board/opt/google/chrome/chrome",
"/build/board",
found_files,
)
)
self.assertIn(test.expected_error, cm.output[0])
self.assertEqual(len(cm.output), 1)
def testValidLibcSymbolFile(self) -> None:
"""Test that a libc.so symbol file can pass the additional checks."""
with multiprocessing.Manager() as mp_manager:
sym_file = self.tempdir / "libc.so.sym"
self._CreateSymbolFile(
sym_file, public_lines=200, stack_lines=20000
)
found_files = mp_manager.list()
self.assertTrue(
cros_generate_breakpad_symbols.ValidateSymbolFile(
str(sym_file),
"/build/board/lib64/libc.so.6",
"/build/board",
found_files,
)
)
self.assertEqual(
list(found_files),
[cros_generate_breakpad_symbols.ExpectedFiles.LIBC],
)
def testInvalidLibcSymbolFile(self) -> None:
"""Test that a libc.so symbol file is held to higher standards."""
class LibcSymbolFileTest:
"""Defines the subtest for an invalid libc symbol file."""
def __init__(
self,
name,
expected_error,
public_lines=200,
stack_lines=20000,
) -> None:
self.name = name
self.expected_error = expected_error
self.public_lines = public_lines
self.stack_lines = stack_lines
LIBC_SYMBOL_TESTS = [
LibcSymbolFileTest(
name="Insufficient PUBLIC records",
public_lines=50,
expected_error="/build/board/lib64/libc.so.6 should have at "
"least 100 PUBLIC records, found 50",
),
LibcSymbolFileTest(
name="Insufficient STACK records",
stack_lines=1000,
expected_error="/build/board/lib64/libc.so.6 should have at "
"least 10000 STACK records, found 1000",
),
]
for test in LIBC_SYMBOL_TESTS:
with self.subTest(
name=test.name
), multiprocessing.Manager() as mp_manager:
sym_file = self.tempdir / "libc.so.sym"
self._CreateSymbolFile(
sym_file,
public_lines=test.public_lines,
stack_lines=test.stack_lines,
)
found_files = mp_manager.list()
with self.assertLogs(level=logging.WARNING) as cm:
self.assertFalse(
cros_generate_breakpad_symbols.ValidateSymbolFile(
str(sym_file),
"/build/board/lib64/libc.so.6",
"/build/board",
found_files,
)
)
self.assertIn(test.expected_error, cm.output[0])
self.assertEqual(len(cm.output), 1)
def testValidCrashReporterSymbolFile(self) -> None:
"""Test a crash_reporter symbol file can pass the additional checks."""
with multiprocessing.Manager() as mp_manager:
sym_file = self.tempdir / "crash_reporter.sym"
self._CreateSymbolFile(
sym_file,
func_lines=2000,
public_lines=10,
stack_lines=2000,
line_number_lines=20000,
)
found_files = mp_manager.list()
self.assertTrue(
cros_generate_breakpad_symbols.ValidateSymbolFile(
str(sym_file),
"/build/board/sbin/crash_reporter",
"/build/board",
found_files,
)
)
self.assertEqual(
list(found_files),
[cros_generate_breakpad_symbols.ExpectedFiles.CRASH_REPORTER],
)
def testInvalidCrashReporterSymbolFile(self) -> None:
"""Test that a crash_reporter symbol file is held to higher standards"""
class CrashReporterSymbolFileTest:
"""Defines the subtest for an invalid crash_reporter symbol file."""
def __init__(
self,
name,
expected_error,
func_lines=2000,
stack_lines=2000,
line_number_lines=20000,
) -> None:
self.name = name
self.expected_error = expected_error
self.func_lines = func_lines
self.stack_lines = stack_lines
self.line_number_lines = line_number_lines
CRASH_REPORTER_SYMBOL_TESTS = [
CrashReporterSymbolFileTest(
name="Insufficient FUNC records",
func_lines=500,
expected_error="crash_reporter should have at least 1000 FUNC "
"records, found 500",
),
CrashReporterSymbolFileTest(
name="Insufficient STACK records",
stack_lines=100,
expected_error="crash_reporter should have at least 1000 STACK "
"records, found 100",
),
CrashReporterSymbolFileTest(
name="Insufficient line number records",
line_number_lines=2000,
expected_error="crash_reporter should have at least 10,000 "
"line number records, found 2000",
),
]
for test in CRASH_REPORTER_SYMBOL_TESTS:
with self.subTest(
name=test.name
), multiprocessing.Manager() as mp_manager:
sym_file = self.tempdir / "crash_reporter.sym"
self._CreateSymbolFile(
sym_file,
func_lines=test.func_lines,
stack_lines=test.stack_lines,
line_number_lines=test.line_number_lines,
)
found_files = mp_manager.list()
with self.assertLogs(level=logging.WARNING) as cm:
self.assertFalse(
cros_generate_breakpad_symbols.ValidateSymbolFile(
str(sym_file),
"/build/board/sbin/crash_reporter",
"/build/board",
found_files,
)
)
self.assertIn(test.expected_error, cm.output[0])
self.assertEqual(len(cm.output), 1)
def testValidLibMetricsSymbolFile(self) -> None:
"""Test a libmetrics.so symbol file can pass the additional checks."""
with multiprocessing.Manager() as mp_manager:
sym_file = self.tempdir / "libmetrics.so.sym"
self._CreateSymbolFile(
sym_file,
func_lines=200,
public_lines=2,
stack_lines=2000,
line_number_lines=10000,
)
found_files = mp_manager.list()
self.assertTrue(
cros_generate_breakpad_symbols.ValidateSymbolFile(
str(sym_file),
"/build/board/usr/lib64/libmetrics.so",
"/build/board",
found_files,
)
)
self.assertEqual(
list(found_files),
[cros_generate_breakpad_symbols.ExpectedFiles.LIBMETRICS],
)
def testInvalidLibMetricsSymbolFile(self) -> None:
"""Test that a libmetrics.so symbol file is held to higher standards."""
class LibMetricsSymbolFileTest:
"""Defines the subtest for an invalid libmetrics.so symbol file."""
def __init__(
self,
name,
expected_error,
func_lines=200,
public_lines=2,
stack_lines=2000,
line_number_lines=10000,
) -> None:
self.name = name
self.expected_error = expected_error
self.func_lines = func_lines
self.public_lines = public_lines
self.stack_lines = stack_lines
self.line_number_lines = line_number_lines
LIBMETRICS_SYMBOL_TESTS = [
LibMetricsSymbolFileTest(
name="Insufficient FUNC records",
func_lines=10,
expected_error="libmetrics should have at least 100 FUNC "
"records, found 10",
),
LibMetricsSymbolFileTest(
name="Insufficient PUBLIC records",
public_lines=0,
expected_error="libmetrics should have at least 1 PUBLIC "
"record, found 0",
),
LibMetricsSymbolFileTest(
name="Insufficient STACK records",
stack_lines=500,
expected_error="libmetrics should have at least 1000 STACK "
"records, found 500",
),
LibMetricsSymbolFileTest(
name="Insufficient line number records",
line_number_lines=2000,
expected_error="libmetrics should have at least 5000 "
"line number records, found 2000",
),
]
for test in LIBMETRICS_SYMBOL_TESTS:
with self.subTest(
name=test.name
), multiprocessing.Manager() as mp_manager:
sym_file = self.tempdir / "libmetrics.so.sym"
self._CreateSymbolFile(
sym_file,
func_lines=test.func_lines,
public_lines=test.public_lines,
stack_lines=test.stack_lines,
line_number_lines=test.line_number_lines,
)
found_files = mp_manager.list()
with self.assertLogs(level=logging.WARNING) as cm:
self.assertFalse(
cros_generate_breakpad_symbols.ValidateSymbolFile(
str(sym_file),
"/build/board/usr/lib64/libmetrics.so",
"/build/board",
found_files,
)
)
self.assertIn(test.expected_error, cm.output[0])
self.assertEqual(len(cm.output), 1)
class UtilsTestDir(cros_test_lib.TempDirTestCase):
"""Tests ReadSymsHeader."""
def testReadSymsHeaderGoodFile(self) -> None:
"""Make sure ReadSymsHeader can parse sym files"""
sym_file = os.path.join(self.tempdir, "sym")
osutils.WriteFile(sym_file, "MODULE Linux x86 s0m31D chrooome")
result = cros_generate_breakpad_symbols.ReadSymsHeader(
sym_file, "unused_elfname"
)
self.assertEqual(result.cpu, "x86")
self.assertEqual(result.id, "s0m31D")
self.assertEqual(result.name, "chrooome")
self.assertEqual(result.os, "Linux")
class UtilsTest(cros_test_lib.TestCase):
"""Tests ReadSymsHeader."""
def testReadSymsHeaderGoodBuffer(self) -> None:
"""Make sure ReadSymsHeader can parse sym file handles"""
result = cros_generate_breakpad_symbols.ReadSymsHeader(
io.BytesIO(b"MODULE Linux arm MY-ID-HERE blkid"), "unused_elfname"
)
self.assertEqual(result.cpu, "arm")
self.assertEqual(result.id, "MY-ID-HERE")
self.assertEqual(result.name, "blkid")
self.assertEqual(result.os, "Linux")
def testReadSymsHeaderBadd(self) -> None:
"""Make sure ReadSymsHeader throws on bad sym files"""
self.assertRaises(
ValueError,
cros_generate_breakpad_symbols.ReadSymsHeader,
io.BytesIO(b"asdf"),
"unused_elfname",
)
def testBreakpadDir(self) -> None:
"""Make sure board->breakpad path expansion works"""
expected = "/build/blah/usr/lib/debug/breakpad"
result = cros_generate_breakpad_symbols.FindBreakpadDir("blah")
self.assertEqual(expected, result)
def testDebugDir(self) -> None:
"""Make sure board->debug path expansion works"""
expected = "/build/blah/usr/lib/debug"
result = cros_generate_breakpad_symbols.FindDebugDir("blah")
self.assertEqual(expected, result)