File: breakpad_file_extractor_unittest.py

package info (click to toggle)
chromium 139.0.7258.127-2
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 6,122,156 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (394 lines) | stat: -rwxr-xr-x 16,583 bytes parent folder | download | duplicates (9)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
#!/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.

from logging import exception
import os
import shutil
import sys
import tempfile
import unittest

import breakpad_file_extractor
import get_symbols_util

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 metadata_extractor

import mock


class ExtractBreakpadTestCase(unittest.TestCase):

  def setUp(self):
    # Create test inputs for ExtractBreakpadFiles() function.
    self.test_build_dir = tempfile.mkdtemp()
    self.test_breakpad_dir = tempfile.mkdtemp()
    self.test_dump_syms_dir = tempfile.mkdtemp()

    # NamedTemporaryFile() is hard coded to have a set of random 8 characters
    # appended to whatever prefix is given. Those characters can't be easily
    # removed, so |self.test_dump_syms_binary| is opened this way.
    self.test_dump_syms_binary = os.path.join(self.test_dump_syms_dir,
                                              'dump_syms')
    with open(self.test_dump_syms_binary, 'w'):
      pass

    # Stash function.
    self.RunDumpSyms_stash = breakpad_file_extractor._RunDumpSyms

  def tearDown(self):
    shutil.rmtree(self.test_build_dir)
    shutil.rmtree(self.test_breakpad_dir)
    shutil.rmtree(self.test_dump_syms_dir)

    # Unstash function.
    breakpad_file_extractor._RunDumpSyms = self.RunDumpSyms_stash

  def _setupSubtreeFiles(self):
    # Create subtree directory structure. All files deleted when
    # |test_breakpad_dir| is recursively deleted.
    out = tempfile.mkdtemp(dir=self.test_breakpad_dir)
    release = tempfile.mkdtemp(dir=out)
    subdir = tempfile.mkdtemp(dir=release)
    unstripped_dir = os.path.join(release, 'lib.unstripped')
    os.mkdir(unstripped_dir)

    # Create symbol files.
    symbol_files = []
    symbol_files.append(os.path.join(subdir, 'subdir.so'))
    symbol_files.append(os.path.join(unstripped_dir, 'unstripped.so'))
    symbol_files.append(os.path.join(unstripped_dir, 'unstripped2.so'))

    for new_file in symbol_files:
      with open(new_file, 'w') as _:
        pass

    # Build side effect mapping.
    side_effect_map = {
        symbol_files[0]:
        'MODULE Android x86_64 34984AB4EF948C0000000000000000000 subdir.so',
        symbol_files[1]: 'MODULE Android x86_64 34984AB4EF948D unstripped.so',
        symbol_files[2]: 'MODULE Android x86_64 34984AB4EF949A unstripped2.so'
    }

    return symbol_files, side_effect_map

  def _getDumpSymsMockSideEffect(self, side_effect_map):
    def run_dumpsyms_side_effect(dump_syms_binary,
                                 input_file_path,
                                 output_file_path,
                                 only_module_header=False):
      self.assertEqual(self.test_dump_syms_binary, dump_syms_binary)
      if only_module_header:
        # Extract Module ID.
        with open(output_file_path, 'w') as f:
          # Write the correct module header into the output f
          f.write(side_effect_map[input_file_path])
      else:
        # Extract breakpads.
        with open(output_file_path, 'w'):
          pass
      return True

    return run_dumpsyms_side_effect

  def _getExpectedModuleExtractionCalls(self, symbol_files):
    expected_module_calls = [
        mock.call(self.test_dump_syms_binary,
                  symbol_fle,
                  mock.ANY,
                  only_module_header=True) for symbol_fle in symbol_files
    ]
    return expected_module_calls

  def _getExpectedBreakpadExtractionCalls(self, extracted_files,
                                          breakpad_files):
    expected_extract_calls = [
        mock.call(self.test_dump_syms_binary, extracted_file,
                  breakpad_files[file_iter])
        for file_iter, extracted_file in enumerate(extracted_files)
    ]
    return expected_extract_calls

  def _getAndEnsureExtractedBreakpadFiles(self, extracted_files):
    breakpad_files = []
    for extracted_file in extracted_files:
      breakpad_filename = os.path.basename(extracted_file) + '.breakpad'
      breakpad_file = os.path.join(self.test_breakpad_dir, breakpad_filename)
      assert (os.path.isfile(breakpad_file))
      breakpad_files.append(breakpad_file)
    return breakpad_files

  def _getAndEnsureExpectedSubtreeBreakpadFiles(self, extracted_files):
    breakpad_files = []
    for extracted_file in extracted_files:
      breakpad_file = extracted_file + '.breakpad'
      assert (os.path.isfile(breakpad_file))
      breakpad_files.append(breakpad_file)
    return breakpad_files

  def _checkExtractWithOneBinary(self, dump_syms_path, build_dir, breakpad_dir):
    # Create test file in |test_build_dir| and test file in |test_breakpad_dir|.
    test_input_file = tempfile.NamedTemporaryFile(suffix='.so', dir=build_dir)
    # |test_output_file_path| requires a specific name, so NamedTemporaryFile()
    # is not used.
    input_file_name = os.path.split(test_input_file.name)[1]
    test_output_file_path = '{output_path}.breakpad'.format(
        output_path=os.path.join(breakpad_dir, input_file_name))
    with open(test_output_file_path, 'w'):
      pass

    # Create tempfiles that should be ignored when extracting symbol files.
    with tempfile.NamedTemporaryFile(
        suffix='.TOC', dir=build_dir), tempfile.NamedTemporaryFile(
            suffix='.java', dir=build_dir), tempfile.NamedTemporaryFile(
                suffix='.zip', dir=build_dir), tempfile.NamedTemporaryFile(
                    suffix='_apk', dir=build_dir), tempfile.NamedTemporaryFile(
                        suffix='.so.dwp',
                        dir=build_dir), tempfile.NamedTemporaryFile(
                            suffix='.so.dwo',
                            dir=build_dir), tempfile.NamedTemporaryFile(
                                suffix='_chromesymbols.zip', dir=build_dir):
      breakpad_file_extractor._RunDumpSyms = mock.MagicMock()
      breakpad_file_extractor.ExtractBreakpadFiles(dump_syms_path, build_dir,
                                                   breakpad_dir)

    breakpad_file_extractor._RunDumpSyms.assert_called_once_with(
        dump_syms_path, test_input_file.name, test_output_file_path)

    # Check that one file exists in the output directory.
    self.assertEqual(len(os.listdir(breakpad_dir)), 1)
    self.assertEqual(
        os.listdir(breakpad_dir)[0],
        os.path.basename(test_input_file.name) + '.breakpad')

  def testOneBinaryFile(self):
    self._checkExtractWithOneBinary(self.test_dump_syms_binary,
                                    self.test_build_dir, self.test_breakpad_dir)

  def testDumpSymsInBuildDir(self):
    new_dump_syms_path = os.path.join(self.test_build_dir, 'dump_syms')
    with open(new_dump_syms_path, 'w'):
      pass
    self._checkExtractWithOneBinary(new_dump_syms_path, self.test_build_dir,
                                    self.test_breakpad_dir)

  def testSymbolsInLibUnstrippedFolder(self):
    os.path.join(self.test_build_dir, 'lib.unstripped')
    self._checkExtractWithOneBinary(self.test_dump_syms_binary,
                                    self.test_build_dir, self.test_breakpad_dir)

  def testMultipleBinaryFiles(self):
    # Create files in |test_build_dir|. All files are removed when
    # |test_build_dir| is recursively deleted.
    symbol_files = []
    so_file = os.path.join(self.test_build_dir, 'test_file.so')
    with open(so_file, 'w') as _:
      pass
    symbol_files.append(so_file)
    exe_file = os.path.join(self.test_build_dir, 'test_file.exe')
    with open(exe_file, 'w') as _:
      pass
    symbol_files.append(exe_file)
    chrome_file = os.path.join(self.test_build_dir, 'chrome')
    with open(chrome_file, 'w') as _:
      pass
    symbol_files.append(chrome_file)

    # Form output file paths.
    breakpad_file_extractor._RunDumpSyms = mock.MagicMock(
        side_effect=self._getDumpSymsMockSideEffect({}))
    breakpad_file_extractor.ExtractBreakpadFiles(self.test_dump_syms_binary,
                                                 self.test_build_dir,
                                                 self.test_breakpad_dir)

    # Check that each expected call to _RunDumpSyms() has been made.
    breakpad_files = self._getAndEnsureExtractedBreakpadFiles(symbol_files)
    expected_calls = self._getExpectedBreakpadExtractionCalls(
        symbol_files, breakpad_files)
    breakpad_file_extractor._RunDumpSyms.assert_has_calls(expected_calls,
                                                          any_order=True)

  def testDumpSymsNotFound(self):
    breakpad_file_extractor._RunDumpSyms = mock.MagicMock()
    exception_msg = 'dump_syms binary not found.'
    with self.assertRaises(Exception) as e:
      breakpad_file_extractor.ExtractBreakpadFiles('fake/path/dump_syms',
                                                   self.test_build_dir,
                                                   self.test_breakpad_dir)
    self.assertIn(exception_msg, str(e.exception))

  def testFakeDirectories(self):
    breakpad_file_extractor._RunDumpSyms = mock.MagicMock()
    exception_msg = 'Invalid breakpad output directory'
    with self.assertRaises(Exception) as e:
      breakpad_file_extractor.ExtractBreakpadFiles(self.test_dump_syms_binary,
                                                   self.test_build_dir,
                                                   'fake_breakpad_dir')
    self.assertIn(exception_msg, str(e.exception))

    exception_msg = 'Invalid build directory'
    with self.assertRaises(Exception) as e:
      breakpad_file_extractor.ExtractBreakpadFiles(self.test_dump_syms_binary,
                                                   'fake_binary_dir',
                                                   self.test_breakpad_dir)
    self.assertIn(exception_msg, str(e.exception))

  def testSymbolizedNoFiles(self):
    did_extract = breakpad_file_extractor.ExtractBreakpadFiles(
        self.test_dump_syms_binary, self.test_build_dir, self.test_breakpad_dir)
    self.assertFalse(did_extract)

  def testNotSearchUnstripped(self):
    # Make 'lib.unstripped' directory and file. Our script should not run
    # dump_syms on this file.
    lib_unstripped = os.path.join(self.test_build_dir, 'lib.unstripped')
    os.mkdir(lib_unstripped)
    lib_unstripped_file = os.path.join(lib_unstripped, 'unstripped.so')
    with open(lib_unstripped_file, 'w') as _:
      pass

    # Make file to run dump_syms on in input directory.
    extracted_file_name = 'extracted.so'
    extracted_file = os.path.join(self.test_build_dir, extracted_file_name)
    with open(extracted_file, 'w') as _:
      pass

    breakpad_file_extractor._RunDumpSyms = mock.MagicMock()
    breakpad_file_extractor.ExtractBreakpadFiles(self.test_dump_syms_binary,
                                                 self.test_build_dir,
                                                 self.test_breakpad_dir,
                                                 search_unstripped=False)

    # Check that _RunDumpSyms() only called for extracted file and not the
    # lib.unstripped files.
    extracted_output_path = '{output_path}.breakpad'.format(
        output_path=os.path.join(self.test_breakpad_dir, extracted_file_name))
    breakpad_file_extractor._RunDumpSyms.assert_called_once_with(
        self.test_dump_syms_binary, extracted_file, extracted_output_path)

  def testIgnorePartitionFiles(self):
    partition_file = os.path.join(self.test_build_dir, 'partition.so')
    with open(partition_file, 'w') as file1:
      file1.write(
          'MODULE Linux x86_64 34984AB4EF948C0000000000000000000 name1.so')

    did_extract = breakpad_file_extractor.ExtractBreakpadFiles(
        self.test_dump_syms_binary, self.test_build_dir, self.test_breakpad_dir)
    self.assertFalse(did_extract)

    os.remove(partition_file)

  def testIgnoreCombinedFiles(self):
    combined_file1 = os.path.join(self.test_build_dir, 'chrome_combined.so')
    combined_file2 = os.path.join(self.test_build_dir, 'libchrome_combined.so')
    with open(combined_file1, 'w') as file1:
      file1.write(
          'MODULE Linux x86_64 34984AB4EF948C0000000000000000000 name1.so')
    with open(combined_file2, 'w') as file2:
      file2.write(
          'MODULE Linux x86_64 34984AB4EF948C0000000000000000000 name2.so')

    did_extract = breakpad_file_extractor.ExtractBreakpadFiles(
        self.test_dump_syms_binary, self.test_build_dir, self.test_breakpad_dir)
    self.assertFalse(did_extract)

    os.remove(combined_file1)
    os.remove(combined_file2)

  def testExtractOnSubtree(self):
    # Setup subtree symbol files.
    symbol_files, side_effect_map = self._setupSubtreeFiles()
    subdir_symbols = symbol_files[0]
    unstripped_symbols = symbol_files[1]

    # Setup metadata.
    metadata = metadata_extractor.MetadataExtractor('trace_processor_shell',
                                                    'trace_file.proto')
    metadata.InitializeForTesting(
        modules={
            '/subdir.so': '34984AB4EF948D',
            '/unstripped.so': '34984AB4EF948C0000000000000000000'
        })
    extracted_files = [subdir_symbols, unstripped_symbols]

    # Setup |_RunDumpSyms| mock for module ID optimization.
    breakpad_file_extractor._RunDumpSyms = mock.MagicMock(
        side_effect=self._getDumpSymsMockSideEffect(side_effect_map))
    breakpad_file_extractor.ExtractBreakpadOnSubtree(self.test_breakpad_dir,
                                                     metadata,
                                                     self.test_dump_syms_binary)

    # Ensure correct |_RunDumpSyms| calls.
    expected_module_calls = self._getExpectedModuleExtractionCalls(symbol_files)

    breakpad_files = self._getAndEnsureExpectedSubtreeBreakpadFiles(
        extracted_files)
    expected_extract_calls = self._getExpectedBreakpadExtractionCalls(
        extracted_files, breakpad_files)

    breakpad_file_extractor._RunDumpSyms.assert_has_calls(
        expected_module_calls + expected_extract_calls, any_order=True)

  def testSubtreeNoFilesExtracted(self):
    # Setup subtree symbol files. No files to be extracted.
    symbol_files, side_effect_map = self._setupSubtreeFiles()

    # Empty set of module IDs to extract. Nothing should be extracted.
    metadata = metadata_extractor.MetadataExtractor('trace_processor_shell',
                                                    'trace_file.proto')
    metadata.InitializeForTesting(modules={})

    # Setup |_RunDumpSyms| mock for module ID optimization.
    breakpad_file_extractor._RunDumpSyms = mock.MagicMock(
        side_effect=self._getDumpSymsMockSideEffect(side_effect_map))
    exception_msg = (
        'No breakpad symbols could be extracted from files in the subtree: ' +
        self.test_breakpad_dir)
    with self.assertRaises(Exception) as e:
      breakpad_file_extractor.ExtractBreakpadOnSubtree(
          self.test_breakpad_dir, metadata, self.test_dump_syms_binary)
    self.assertIn(exception_msg, str(e.exception))

    # Should be calls to extract module ID, but none to extract breakpad.
    expected_module_calls = self._getExpectedModuleExtractionCalls(symbol_files)
    breakpad_file_extractor._RunDumpSyms.assert_has_calls(expected_module_calls,
                                                          any_order=True)

  def testFindOnSubtree(self):
    # Setup subtree symbol files.
    _, side_effect_map = self._setupSubtreeFiles()

    # Setup |_RunDumpSyms| mock for module ID optimization.
    breakpad_file_extractor._RunDumpSyms = mock.MagicMock(
        side_effect=self._getDumpSymsMockSideEffect(side_effect_map))

    found = get_symbols_util.FindMatchingModule(
        self.test_breakpad_dir, self.test_dump_syms_binary,
        '34984AB4EF948C0000000000000000000')
    self.assertIn('subdir.so', found)

  def testNotFindOnSubtree(self):
    # Setup subtree symbol files.
    _, side_effect_map = self._setupSubtreeFiles()

    # Setup |_RunDumpSyms| mock for module ID optimization.
    breakpad_file_extractor._RunDumpSyms = mock.MagicMock(
        side_effect=self._getDumpSymsMockSideEffect(side_effect_map))

    found = get_symbols_util.FindMatchingModule(self.test_breakpad_dir,
                                                self.test_dump_syms_binary,
                                                'NOTFOUND')
    self.assertIsNone(found)


if __name__ == '__main__':
  unittest.main()