#!/usr/bin/env vpython3
# Copyright 2021 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
import sys
import unittest
from unittest import mock
import symbolize_trace
import symbol_fetcher
import metadata_extractor
import breakpad_file_extractor
import tempfile
import shutil
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir, 'perf'))
from core import path_util
path_util.AddPyUtilsToPath()
path_util.AddTracingToPath()
class TestOptions():
def __init__(self):
self.trace_processor_path = None
self.dump_syms_path = None
self.local_build_dir = None
self.breakpad_output_dir = None
self.local_breakpad_dir = None
self.breakpad_output_dir = None
self.cloud_storage_bucket = None
self.output_file = None
self.symbolizer_path = None
class SymbolizeTraceTestCase(unittest.TestCase):
def side_effect(self, cmd, env, stdout):
if cmd and env:
stdout.write(b'Symbol data.')
def setUp(self):
self.options = TestOptions()
# Function stashing so mocking doesn't mutate other tests.
self.RunSymbolizer = symbolize_trace._RunSymbolizer
self.GetTraceBreakpadSymbols = symbol_fetcher.GetTraceBreakpadSymbols
self.MetadataExtractor = metadata_extractor.MetadataExtractor
self.ExtractBreakpadFiles = breakpad_file_extractor.ExtractBreakpadFiles
symbolize_trace._RunSymbolizer = mock.MagicMock(
side_effect=self.side_effect)
symbol_fetcher.GetTraceBreakpadSymbols = mock.MagicMock()
metadata_extractor.MetadataExtractor = mock.MagicMock()
breakpad_file_extractor.ExtractBreakpadFiles = mock.MagicMock()
dump_syms_dir = tempfile.mkdtemp()
self.options.dump_syms_path = os.path.join(dump_syms_dir, 'dump_syms')
with open(self.options.dump_syms_path, 'w') as _:
pass
with tempfile.NamedTemporaryFile(mode='w+',
delete=False) as test_trace_file:
test_trace_file.write('Trace data.')
self.trace_file = test_trace_file.name
def tearDown(self):
os.remove(self.trace_file)
# Unstash functions.
symbolize_trace._RunSymbolizer = self.RunSymbolizer
symbol_fetcher.GetTraceBreakpadSymbols = self.GetTraceBreakpadSymbols
metadata_extractor.MetadataExtractor = self.MetadataExtractor
breakpad_file_extractor.ExtractBreakpadFiles = self.ExtractBreakpadFiles
def testNoLocalOrOutputBreakpadDir(self):
# Test the case with no breakpad output directory specified.
symbolize_trace.SymbolizeTrace(self.trace_file, self.options)
metadata_extractor.MetadataExtractor.assert_called_once()
symbol_fetcher.GetTraceBreakpadSymbols.assert_called_once()
breakpad_file_extractor.ExtractBreakpadFiles.assert_not_called()
symbolize_trace._RunSymbolizer.assert_called_once()
# Check that symbolized trace file was written correctly.
self.assertEqual(
self.options.output_file,
os.path.join(os.path.dirname(self.trace_file),
os.path.basename(self.trace_file) + '_symbolized_trace'))
with open(self.options.output_file, 'r') as f:
symbolized_trace_data = f.read()
self.assertEqual(symbolized_trace_data, 'Trace data.Symbol data.')
# Remove files.
os.remove(self.options.output_file)
def testNoLocalBreakpadDirAndInvalidOutputDir(self):
self.options.breakpad_output_dir = 'fake/directory'
symbolize_trace.SymbolizeTrace(self.trace_file, self.options)
metadata_extractor.MetadataExtractor.assert_called_once()
symbol_fetcher.GetTraceBreakpadSymbols.assert_called_once()
breakpad_file_extractor.ExtractBreakpadFiles.assert_not_called()
symbolize_trace._RunSymbolizer.assert_called_once()
# Check that symbolized trace file was written correctly.
self.assertEqual(
self.options.output_file,
os.path.join(os.path.dirname(self.trace_file),
os.path.basename(self.trace_file) + '_symbolized_trace'))
with open(self.options.output_file, 'r') as f:
symbolized_trace_data = f.read()
self.assertEqual(symbolized_trace_data, 'Trace data.Symbol data.')
# Remove files and temp directory.
os.remove(self.options.output_file)
shutil.rmtree(self.options.breakpad_output_dir)
def testNoLocalBreakpadDirAndValidOutputDir(self):
self.options.breakpad_output_dir = tempfile.mkdtemp()
symbolize_trace.SymbolizeTrace(self.trace_file, self.options)
metadata_extractor.MetadataExtractor.assert_called_once()
symbol_fetcher.GetTraceBreakpadSymbols.assert_called_once()
breakpad_file_extractor.ExtractBreakpadFiles.assert_not_called()
symbolize_trace._RunSymbolizer.assert_called_once()
# Check that symbolized trace file was written correctly.
self.assertEqual(
self.options.output_file,
os.path.join(os.path.dirname(self.trace_file),
os.path.basename(self.trace_file) + '_symbolized_trace'))
with open(self.options.output_file, 'r') as f:
symbolized_trace_data = f.read()
self.assertEqual(symbolized_trace_data, 'Trace data.Symbol data.')
# Remove files and temp directory.
os.remove(self.options.output_file)
shutil.rmtree(self.options.breakpad_output_dir)
def testNoLocalBreakpadDirAndNonEmptyBreakpadOutputDir(self):
self.options.breakpad_output_dir = tempfile.mkdtemp()
# Check that exception is thrown for non-empty breakpad output directory.
exception_msg = 'Breakpad output directory is not empty:'
with tempfile.NamedTemporaryFile(dir=self.options.breakpad_output_dir):
with self.assertRaises(Exception) as e:
symbolize_trace.SymbolizeTrace(self.trace_file, self.options)
self.assertIn(exception_msg, str(e.exception))
# Remove files and temp directory.
shutil.rmtree(self.options.breakpad_output_dir)
def testInvalidLocalBreakpadDir(self):
self.options.local_breakpad_dir = 'fake/directory'
exception_msg = 'Local breakpad directory is not valid.'
with self.assertRaises(Exception) as e:
symbolize_trace.SymbolizeTrace(self.trace_file, self.options)
self.assertIn(exception_msg, str(e.exception))
def testFailWhenNoDumpSyms(self):
self.options.dump_syms_path = None
exception_msg = 'dump_syms binary not found.'
with self.assertRaises(Exception) as e:
symbolize_trace.SymbolizeTrace(self.trace_file, self.options)
self.assertIn(exception_msg, str(e.exception))
def testFindDumpSymsInBuild(self):
self.options.local_build_dir = tempfile.mkdtemp()
self.options.dump_syms_path = None
dump_syms_path = os.path.join(self.options.local_build_dir, 'dump_syms')
with open(dump_syms_path, 'w') as _:
pass
# Throws no exception
symbolize_trace.SymbolizeTrace(self.trace_file, self.options)
def testValidLocalBreakpadDir(self):
self.options.local_breakpad_dir = tempfile.mkdtemp()
symbolize_trace.SymbolizeTrace(self.trace_file, self.options)
metadata_extractor.MetadataExtractor.assert_not_called()
symbol_fetcher.GetTraceBreakpadSymbols.assert_not_called()
breakpad_file_extractor.ExtractBreakpadFiles.assert_not_called()
symbolize_trace._RunSymbolizer.assert_called_once()
# Check that symbolized trace file was written correctly.
self.assertEqual(
self.options.output_file,
os.path.join(os.path.dirname(self.trace_file),
os.path.basename(self.trace_file) + '_symbolized_trace'))
with open(self.options.output_file, 'r') as f:
symbolized_trace_data = f.read()
self.assertEqual(symbolized_trace_data, 'Trace data.Symbol data.')
# Remove files and temp directory.
os.remove(self.options.output_file)
shutil.rmtree(self.options.local_breakpad_dir)
def testValidLocalBuildDir(self):
self.options.local_build_dir = tempfile.mkdtemp()
symbolize_trace.SymbolizeTrace(self.trace_file, self.options)
symbol_fetcher.GetTraceBreakpadSymbols.assert_not_called()
metadata_extractor.MetadataExtractor.assert_called_once()
breakpad_file_extractor.ExtractBreakpadFiles.assert_called_once()
symbolize_trace._RunSymbolizer.assert_called_once()
# Check that symbolized trace file was written correctly.
self.assertEqual(
self.options.output_file,
os.path.join(os.path.dirname(self.trace_file),
os.path.basename(self.trace_file) + '_symbolized_trace'))
with open(self.options.output_file, 'r') as f:
symbolized_trace_data = f.read()
self.assertEqual(symbolized_trace_data, 'Trace data.Symbol data.')
# Remove files and temp directory.
os.remove(self.options.output_file)
shutil.rmtree(self.options.local_build_dir)
def testValidLocalBuildAndBreakpadDir(self):
self.options.local_build_dir = tempfile.mkdtemp()
self.options.local_breakpad_dir = tempfile.mkdtemp()
symbolize_trace.SymbolizeTrace(self.trace_file, self.options)
metadata_extractor.MetadataExtractor.assert_not_called()
symbol_fetcher.GetTraceBreakpadSymbols.assert_not_called()
breakpad_file_extractor.ExtractBreakpadFiles.assert_not_called()
symbolize_trace._RunSymbolizer.assert_called_once()
# Check that symbolized trace file was written correctly.
self.assertEqual(
self.options.output_file,
os.path.join(os.path.dirname(self.trace_file),
os.path.basename(self.trace_file) + '_symbolized_trace'))
with open(self.options.output_file, 'r') as f:
symbolized_trace_data = f.read()
self.assertEqual(symbolized_trace_data, 'Trace data.Symbol data.')
# Remove files and temp directory.
os.remove(self.options.output_file)
shutil.rmtree(self.options.local_build_dir)
shutil.rmtree(self.options.local_breakpad_dir)
def testOutputFileGiven(self):
self.options.local_breakpad_dir = tempfile.mkdtemp()
self.options.output_file = os.path.join(os.path.dirname(self.trace_file),
'output_file')
symbolize_trace.SymbolizeTrace(self.trace_file, self.options)
metadata_extractor.MetadataExtractor.assert_not_called()
symbol_fetcher.GetTraceBreakpadSymbols.assert_not_called()
breakpad_file_extractor.ExtractBreakpadFiles.assert_not_called()
symbolize_trace._RunSymbolizer.assert_called_once()
# Check that symbolized trace file was written correctly.
self.assertEqual(
self.options.output_file,
os.path.join(os.path.dirname(self.trace_file), 'output_file'))
with open(self.options.output_file, 'r') as f:
symbolized_trace_data = f.read()
self.assertEqual(symbolized_trace_data, 'Trace data.Symbol data.')
# Remove files and temp directory.
os.remove(self.options.output_file)
shutil.rmtree(self.options.local_breakpad_dir)
def testLocalNoBreakpadExtracted(self):
# Unmock breakpad extraction function.
breakpad_file_extractor.ExtractBreakpadFiles = self.ExtractBreakpadFiles
# Set up option arguments to run extract breakpad on local build directory.
self.options.breakpad_output_dir = tempfile.mkdtemp()
self.options.local_build_dir = tempfile.mkdtemp()
trace_file_override = None
dump_syms_dir = tempfile.mkdtemp()
self.options.dump_syms_path = os.path.join(dump_syms_dir, 'dump_syms')
with open(self.options.dump_syms_path, 'w') as _:
pass
unstripped_dir = os.path.join(self.options.local_build_dir,
'lib.unstripped')
exception_msg = (
'No breakpad symbols could be extracted from files in: %s xor %s' %
(self.options.local_build_dir, unstripped_dir))
# Test when there is no 'lib.unstripped' subdirectory.
with self.assertRaises(Exception) as e:
symbolize_trace.SymbolizeTrace(trace_file_override, self.options)
self.assertIn(exception_msg, str(e.exception))
# Test when there is a 'lib.unstripped' subdirectory.
os.mkdir(unstripped_dir)
with self.assertRaises(Exception):
symbolize_trace.SymbolizeTrace(trace_file_override, self.options)
# Remove files and temp directory.
shutil.rmtree(self.options.local_build_dir)
shutil.rmtree(dump_syms_dir)
shutil.rmtree(self.options.breakpad_output_dir)
if __name__ == '__main__':
unittest.main()