#!/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 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() import mock 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) metadata_extractor.MetadataExtractor.assert_not_called() symbol_fetcher.GetTraceBreakpadSymbols.assert_not_called() 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()