2 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 """Test cros_generate_breakpad_symbols."""
8 from __future__ import print_function
16 sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)),
18 from chromite.lib import cros_build_lib_unittest
19 from chromite.lib import cros_test_lib
20 from chromite.lib import osutils
21 from chromite.lib import parallel
22 from chromite.lib import parallel_unittest
23 from chromite.lib import partial_mock
24 from chromite.scripts import cros_generate_breakpad_symbols
26 # TODO(build): Finish test wrapper (http://crosbug.com/37517).
27 # Until then, this has to be after the chromite imports.
31 class FindDebugDirMock(partial_mock.PartialMock):
32 """Mock out the DebugDir helper so we can point it to a tempdir."""
34 TARGET = 'chromite.scripts.cros_generate_breakpad_symbols'
35 ATTRS = ('FindDebugDir',)
36 DEFAULT_ATTR = 'FindDebugDir'
38 def __init__(self, path, *args, **kwargs):
40 super(FindDebugDirMock, self).__init__(*args, **kwargs)
42 def FindDebugDir(self, _board):
46 @mock.patch('chromite.scripts.cros_generate_breakpad_symbols.'
47 'GenerateBreakpadSymbol')
48 class GenerateSymbolsTest(cros_test_lib.MockTempDirTestCase):
49 """Test GenerateBreakpadSymbols."""
52 self.board = 'monkey-board'
53 self.board_dir = os.path.join(self.tempdir, 'build', self.board)
54 self.debug_dir = os.path.join(self.board_dir, 'usr', 'lib', 'debug')
55 self.breakpad_dir = os.path.join(self.debug_dir, 'breakpad')
57 # Generate a tree of files which we'll scan through.
61 # Need some kernel modules (with & without matching .debug).
62 'lib/modules/3.10/module.ko',
63 'lib/modules/3.10/module-no-debug.ko',
64 # Need a file which has an ELF only, but not a .debug.
71 'iii/large-elf.debug',
72 'lib/modules/3.10/module.ko.debug',
73 # Need a file which has a .debug only, but not an ELF.
74 'sbin/debug-only.debug',
77 for f in ([os.path.join(self.board_dir, x) for x in elf_files] +
78 [os.path.join(self.debug_dir, x) for x in debug_files]):
79 osutils.Touch(f, makedirs=True)
81 # Set up random build dirs and symlinks.
82 buildid = os.path.join(self.debug_dir, '.build-id', '00')
83 osutils.SafeMakedirs(buildid)
84 os.symlink('/asdf', os.path.join(buildid, 'foo'))
85 os.symlink('/bin/sh', os.path.join(buildid, 'foo.debug'))
86 os.symlink('/bin/sh', os.path.join(self.debug_dir, 'file.debug'))
87 osutils.WriteFile(os.path.join(self.debug_dir, 'iii', 'large-elf.debug'),
90 self.StartPatcher(FindDebugDirMock(self.debug_dir))
92 def testNormal(self, gen_mock):
93 """Verify all the files we expect to get generated do"""
94 with parallel_unittest.ParallelMock():
95 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
96 self.board, sysroot=self.board_dir)
97 self.assertEquals(ret, 0)
98 self.assertEquals(gen_mock.call_count, 3)
100 # The largest ELF should be processed first.
101 call1 = (os.path.join(self.board_dir, 'iii/large-elf'),
102 os.path.join(self.debug_dir, 'iii/large-elf.debug'))
103 self.assertEquals(gen_mock.call_args_list[0][0], call1)
105 # The other ELFs can be called in any order.
106 call2 = (os.path.join(self.board_dir, 'bin/elf'),
107 os.path.join(self.debug_dir, 'bin/elf.debug'))
108 call3 = (os.path.join(self.board_dir, 'usr/sbin/elf'),
109 os.path.join(self.debug_dir, 'usr/sbin/elf.debug'))
110 exp_calls = set((call2, call3))
111 actual_calls = set((gen_mock.call_args_list[1][0],
112 gen_mock.call_args_list[2][0]))
113 self.assertEquals(exp_calls, actual_calls)
115 def testFileList(self, gen_mock):
116 """Verify that file_list restricts the symbols generated"""
117 with parallel_unittest.ParallelMock():
118 call1 = (os.path.join(self.board_dir, 'usr/sbin/elf'),
119 os.path.join(self.debug_dir, 'usr/sbin/elf.debug'))
121 # Filter with elf path.
122 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
123 self.board, sysroot=self.board_dir, breakpad_dir=self.breakpad_dir,
124 file_list=[os.path.join(self.board_dir, 'usr', 'sbin', 'elf')])
125 self.assertEquals(ret, 0)
126 self.assertEquals(gen_mock.call_count, 1)
127 self.assertEquals(gen_mock.call_args_list[0][0], call1)
129 # Filter with debug symbols file path.
130 gen_mock.reset_mock()
131 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
132 self.board, sysroot=self.board_dir, breakpad_dir=self.breakpad_dir,
133 file_list=[os.path.join(self.debug_dir, 'usr', 'sbin', 'elf.debug')])
134 self.assertEquals(ret, 0)
135 self.assertEquals(gen_mock.call_count, 1)
136 self.assertEquals(gen_mock.call_args_list[0][0], call1)
139 def testGenLimit(self, gen_mock):
140 """Verify generate_count arg works"""
141 with parallel_unittest.ParallelMock():
143 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
144 self.board, sysroot=self.board_dir, breakpad_dir=self.breakpad_dir,
146 self.assertEquals(ret, 0)
147 self.assertEquals(gen_mock.call_count, 0)
150 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
151 self.board, sysroot=self.board_dir, breakpad_dir=self.breakpad_dir,
153 self.assertEquals(ret, 0)
154 self.assertEquals(gen_mock.call_count, 1)
156 # The largest ELF should be processed first.
157 call1 = (os.path.join(self.board_dir, 'iii/large-elf'),
158 os.path.join(self.debug_dir, 'iii/large-elf.debug'))
159 self.assertEquals(gen_mock.call_args_list[0][0], call1)
161 def testGenErrors(self, gen_mock):
162 """Verify we handle errors from generation correctly"""
163 def _SetError(*_args, **kwargs):
164 kwargs['num_errors'].value += 1
166 gen_mock.side_effect = _SetError
167 with parallel_unittest.ParallelMock():
168 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
169 self.board, sysroot=self.board_dir)
170 self.assertEquals(ret, 3)
171 self.assertEquals(gen_mock.call_count, 3)
173 def testCleaningTrue(self, gen_mock):
174 """Verify behavior of clean_breakpad=True"""
175 with parallel_unittest.ParallelMock():
176 # Dir does not exist, and then does.
177 self.assertNotExists(self.breakpad_dir)
178 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
179 self.board, sysroot=self.board_dir, generate_count=1,
181 self.assertEquals(ret, 0)
182 self.assertEquals(gen_mock.call_count, 1)
183 self.assertExists(self.breakpad_dir)
185 # Dir exists before & after.
186 # File exists, but then doesn't.
187 dummy_file = os.path.join(self.breakpad_dir, 'fooooooooo')
188 osutils.Touch(dummy_file)
189 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
190 self.board, sysroot=self.board_dir, generate_count=1,
192 self.assertEquals(ret, 0)
193 self.assertEquals(gen_mock.call_count, 2)
194 self.assertNotExists(dummy_file)
196 def testCleaningFalse(self, gen_mock):
197 """Verify behavior of clean_breakpad=False"""
198 with parallel_unittest.ParallelMock():
199 # Dir does not exist, and then does.
200 self.assertNotExists(self.breakpad_dir)
201 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
202 self.board, sysroot=self.board_dir, generate_count=1,
203 clean_breakpad=False)
204 self.assertEquals(ret, 0)
205 self.assertEquals(gen_mock.call_count, 1)
206 self.assertExists(self.breakpad_dir)
208 # Dir exists before & after.
209 # File exists before & after.
210 dummy_file = os.path.join(self.breakpad_dir, 'fooooooooo')
211 osutils.Touch(dummy_file)
212 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
213 self.board, sysroot=self.board_dir, generate_count=1,
214 clean_breakpad=False)
215 self.assertEquals(ret, 0)
216 self.assertEquals(gen_mock.call_count, 2)
217 self.assertExists(dummy_file)
219 def testExclusionList(self, gen_mock):
220 """Verify files in directories of the exclusion list are excluded"""
221 exclude_dirs = ['bin', 'usr', 'fake/dir/fake']
222 with parallel_unittest.ParallelMock():
223 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
224 self.board, sysroot=self.board_dir, exclude_dirs=exclude_dirs)
225 self.assertEquals(ret, 0)
226 self.assertEquals(gen_mock.call_count, 1)
228 class GenerateSymbolTest(cros_test_lib.MockTempDirTestCase):
229 """Test GenerateBreakpadSymbol."""
232 self.elf_file = os.path.join(self.tempdir, 'elf')
233 osutils.Touch(self.elf_file)
234 self.debug_dir = os.path.join(self.tempdir, 'debug')
235 self.debug_file = os.path.join(self.debug_dir, 'elf.debug')
236 osutils.Touch(self.debug_file, makedirs=True)
237 # Not needed as the code itself should create it as needed.
238 self.breakpad_dir = os.path.join(self.debug_dir, 'breakpad')
240 self.rc_mock = self.StartPatcher(cros_build_lib_unittest.RunCommandMock())
241 self.rc_mock.SetDefaultCmdResult(output='MODULE OS CPU ID NAME')
242 self.assertCommandContains = self.rc_mock.assertCommandContains
243 self.sym_file = os.path.join(self.breakpad_dir, 'NAME/ID/NAME.sym')
245 self.StartPatcher(FindDebugDirMock(self.debug_dir))
247 def assertCommandArgs(self, i, args):
248 """Helper for looking at the args of the |i|th call"""
249 self.assertEqual(self.rc_mock.call_args_list[i][0][0], args)
251 def testNormal(self):
252 """Normal run -- given an ELF and a debug file"""
253 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
254 self.elf_file, self.debug_file, breakpad_dir=self.breakpad_dir)
255 self.assertEqual(ret, 0)
256 self.assertEqual(self.rc_mock.call_count, 1)
257 self.assertCommandArgs(0, ['dump_syms', self.elf_file, self.debug_dir])
258 self.assertExists(self.sym_file)
260 def testNormalBoard(self):
261 """Normal run w/board info but not breakpad dir"""
262 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
263 self.elf_file, board='foo')
264 self.assertEqual(ret, 0)
265 self.assertCommandArgs(0, ['dump_syms', self.elf_file])
266 self.assertEqual(self.rc_mock.call_count, 1)
267 self.assertExists(self.sym_file)
269 def testNormalNoCfi(self):
270 """Normal run w/out CFI"""
271 # Make sure the num_errors flag works too.
272 num_errors = ctypes.c_int()
273 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
274 self.elf_file, strip_cfi=True, num_errors=num_errors)
275 self.assertEqual(ret, 0)
276 self.assertEqual(num_errors.value, 0)
277 self.assertCommandArgs(0, ['dump_syms', '-c', self.elf_file])
278 self.assertEqual(self.rc_mock.call_count, 1)
279 self.assertExists(self.sym_file)
281 def testNormalElfOnly(self):
282 """Normal run -- given just an ELF"""
283 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(self.elf_file)
284 self.assertEqual(ret, 0)
285 self.assertCommandArgs(0, ['dump_syms', self.elf_file])
286 self.assertEqual(self.rc_mock.call_count, 1)
287 self.assertExists(self.sym_file)
289 def testNormalSudo(self):
290 """Normal run where ELF is readable only by root"""
291 with mock.patch.object(os, 'access') as mock_access:
292 mock_access.return_value = False
293 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(self.elf_file)
294 self.assertEqual(ret, 0)
295 self.assertCommandArgs(0, ['sudo', '--', 'dump_syms', self.elf_file])
297 def testLargeDebugFail(self):
298 """Running w/large .debug failed, but retry worked"""
299 self.rc_mock.AddCmdResult(['dump_syms', self.elf_file, self.debug_dir],
301 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
302 self.elf_file, self.debug_file)
303 self.assertEqual(ret, 0)
304 self.assertEqual(self.rc_mock.call_count, 2)
305 self.assertCommandArgs(0, ['dump_syms', self.elf_file, self.debug_dir])
306 self.assertCommandArgs(
307 1, ['dump_syms', '-c', '-r', self.elf_file, self.debug_dir])
308 self.assertExists(self.sym_file)
310 def testDebugFail(self):
311 """Running w/.debug always failed, but works w/out"""
312 self.rc_mock.AddCmdResult(['dump_syms', self.elf_file, self.debug_dir],
314 self.rc_mock.AddCmdResult(['dump_syms', '-c', '-r', self.elf_file,
317 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
318 self.elf_file, self.debug_file)
319 self.assertEqual(ret, 0)
320 self.assertEqual(self.rc_mock.call_count, 3)
321 self.assertCommandArgs(0, ['dump_syms', self.elf_file, self.debug_dir])
322 self.assertCommandArgs(
323 1, ['dump_syms', '-c', '-r', self.elf_file, self.debug_dir])
324 self.assertCommandArgs(2, ['dump_syms', self.elf_file])
325 self.assertExists(self.sym_file)
327 def testCompleteFail(self):
328 """Running dump_syms always fails"""
329 self.rc_mock.SetDefaultCmdResult(returncode=1)
330 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(self.elf_file)
331 self.assertEqual(ret, 1)
332 # Make sure the num_errors flag works too.
333 num_errors = ctypes.c_int()
334 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
335 self.elf_file, num_errors=num_errors)
336 self.assertEqual(ret, 1)
337 self.assertEqual(num_errors.value, 1)
340 class UtilsTestDir(cros_test_lib.TempDirTestCase):
341 """Tests ReadSymsHeader."""
343 def testReadSymsHeaderGoodFile(self):
344 """Make sure ReadSymsHeader can parse sym files"""
345 sym_file = os.path.join(self.tempdir, 'sym')
346 osutils.WriteFile(sym_file, 'MODULE Linux x86 s0m31D chrooome')
347 result = cros_generate_breakpad_symbols.ReadSymsHeader(sym_file)
348 self.assertEquals(result.cpu, 'x86')
349 self.assertEquals(result.id, 's0m31D')
350 self.assertEquals(result.name, 'chrooome')
351 self.assertEquals(result.os, 'Linux')
354 class UtilsTest(cros_test_lib.TestCase):
355 """Tests ReadSymsHeader."""
357 def testReadSymsHeaderGoodBuffer(self):
358 """Make sure ReadSymsHeader can parse sym file handles"""
359 result = cros_generate_breakpad_symbols.ReadSymsHeader(
360 StringIO.StringIO('MODULE Linux arm MY-ID-HERE blkid'))
361 self.assertEquals(result.cpu, 'arm')
362 self.assertEquals(result.id, 'MY-ID-HERE')
363 self.assertEquals(result.name, 'blkid')
364 self.assertEquals(result.os, 'Linux')
366 def testReadSymsHeaderBadd(self):
367 """Make sure ReadSymsHeader throws on bad sym files"""
368 self.assertRaises(ValueError, cros_generate_breakpad_symbols.ReadSymsHeader,
369 StringIO.StringIO('asdf'))
371 def testBreakpadDir(self):
372 """Make sure board->breakpad path expansion works"""
373 expected = '/build/blah/usr/lib/debug/breakpad'
374 result = cros_generate_breakpad_symbols.FindBreakpadDir('blah')
375 self.assertEquals(expected, result)
377 def testDebugDir(self):
378 """Make sure board->debug path expansion works"""
379 expected = '/build/blah/usr/lib/debug'
380 result = cros_generate_breakpad_symbols.FindDebugDir('blah')
381 self.assertEquals(expected, result)
384 if __name__ == '__main__':
385 # pylint: disable=W0212
386 # Set timeouts small so that if the unit test hangs, it won't hang for long.
387 parallel._BackgroundTask.STARTUP_TIMEOUT = 5
388 parallel._BackgroundTask.EXIT_TIMEOUT = 5
391 cros_test_lib.main(level=logging.INFO)