blob: 709eaca6ab75b7b746b3564e0d36de3b9c6b4bb4 [file] [log] [blame]
Mike Frysinger69cb41d2013-08-11 20:08:19 -04001#!/usr/bin/python
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.
5
Don Garrett25f309a2014-03-19 14:02:12 -07006"""Test cros_generate_breakpad_symbols."""
7
Mike Frysinger69cb41d2013-08-11 20:08:19 -04008import ctypes
9import logging
10import os
11import StringIO
12import sys
13
14sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)),
15 '..', '..'))
16from chromite.lib import cros_build_lib_unittest
17from chromite.lib import cros_test_lib
18from chromite.lib import osutils
19from chromite.lib import parallel
20from chromite.lib import parallel_unittest
21from chromite.lib import partial_mock
22from chromite.scripts import cros_generate_breakpad_symbols
23
24# TODO(build): Finish test wrapper (http://crosbug.com/37517).
25# Until then, this has to be after the chromite imports.
26import mock
27
28
29class FindDebugDirMock(partial_mock.PartialMock):
30 """Mock out the DebugDir helper so we can point it to a tempdir."""
31
32 TARGET = 'chromite.scripts.cros_generate_breakpad_symbols'
33 ATTRS = ('FindDebugDir',)
34 DEFAULT_ATTR = 'FindDebugDir'
35
Mike Frysingerc15efa52013-12-12 01:13:56 -050036 def __init__(self, path, *args, **kwargs):
Mike Frysinger69cb41d2013-08-11 20:08:19 -040037 self.path = path
Mike Frysingerc15efa52013-12-12 01:13:56 -050038 super(FindDebugDirMock, self).__init__(*args, **kwargs)
Mike Frysinger69cb41d2013-08-11 20:08:19 -040039
40 def FindDebugDir(self, _board):
41 return self.path
42
43
44@mock.patch('chromite.scripts.cros_generate_breakpad_symbols.'
45 'GenerateBreakpadSymbol')
46class GenerateSymbolsTest(cros_test_lib.MockTempDirTestCase):
Don Garrett25f309a2014-03-19 14:02:12 -070047 """Test cros_generate_breakpad_symbols.GenerateBreakpadSymbols."""
Mike Frysinger69cb41d2013-08-11 20:08:19 -040048
49 def setUp(self):
50 self.board = 'monkey-board'
51 self.board_dir = os.path.join(self.tempdir, 'build', self.board)
52 self.debug_dir = os.path.join(self.board_dir, 'usr', 'lib', 'debug')
53 self.breakpad_dir = os.path.join(self.debug_dir, 'breakpad')
54
55 # Generate a tree of files which we'll scan through.
56 elf_files = [
57 'bin/elf',
58 'iii/large-elf',
59 # Need some kernel modules (with & without matching .debug).
60 'lib/modules/3.10/module.ko',
61 'lib/modules/3.10/module-no-debug.ko',
62 # Need a file which has an ELF only, but not a .debug.
63 'usr/bin/elf-only',
64 'usr/sbin/elf',
65 ]
66 debug_files = [
67 'bin/bad-file',
68 'bin/elf.debug',
69 'iii/large-elf.debug',
70 'lib/modules/3.10/module.ko.debug',
71 # Need a file which has a .debug only, but not an ELF.
72 'sbin/debug-only.debug',
73 'usr/sbin/elf.debug',
74 ]
75 for f in ([os.path.join(self.board_dir, x) for x in elf_files] +
76 [os.path.join(self.debug_dir, x) for x in debug_files]):
77 osutils.Touch(f, makedirs=True)
78
79 # Set up random build dirs and symlinks.
80 buildid = os.path.join(self.debug_dir, '.build-id', '00')
81 osutils.SafeMakedirs(buildid)
82 os.symlink('/asdf', os.path.join(buildid, 'foo'))
83 os.symlink('/bin/sh', os.path.join(buildid, 'foo.debug'))
84 os.symlink('/bin/sh', os.path.join(self.debug_dir, 'file.debug'))
85 osutils.WriteFile(os.path.join(self.debug_dir, 'iii', 'large-elf.debug'),
86 'just some content')
87
88 self.StartPatcher(FindDebugDirMock(self.debug_dir))
89
90 def testNormal(self, gen_mock):
91 """Verify all the files we expect to get generated do"""
92 with parallel_unittest.ParallelMock():
93 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
94 self.board, sysroot=self.board_dir)
95 self.assertEquals(ret, 0)
96 self.assertEquals(gen_mock.call_count, 3)
97
98 # The largest ELF should be processed first.
99 call1 = (os.path.join(self.board_dir, 'iii/large-elf'),
100 os.path.join(self.debug_dir, 'iii/large-elf.debug'))
101 self.assertEquals(gen_mock.call_args_list[0][0], call1)
102
103 # The other ELFs can be called in any order.
104 call2 = (os.path.join(self.board_dir, 'bin/elf'),
105 os.path.join(self.debug_dir, 'bin/elf.debug'))
106 call3 = (os.path.join(self.board_dir, 'usr/sbin/elf'),
107 os.path.join(self.debug_dir, 'usr/sbin/elf.debug'))
108 exp_calls = set((call2, call3))
109 actual_calls = set((gen_mock.call_args_list[1][0],
110 gen_mock.call_args_list[2][0]))
111 self.assertEquals(exp_calls, actual_calls)
112
Prathmesh Prabhu9995e9b2013-10-31 16:43:55 -0700113 def testFileList(self, gen_mock):
114 """Verify that file_list restricts the symbols generated"""
115 with parallel_unittest.ParallelMock():
116 call1 = (os.path.join(self.board_dir, 'usr/sbin/elf'),
117 os.path.join(self.debug_dir, 'usr/sbin/elf.debug'))
118
119 # Filter with elf path.
120 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
121 self.board, sysroot=self.board_dir, breakpad_dir=self.breakpad_dir,
122 file_list=[os.path.join(self.board_dir, 'usr', 'sbin', 'elf')])
123 self.assertEquals(ret, 0)
124 self.assertEquals(gen_mock.call_count, 1)
125 self.assertEquals(gen_mock.call_args_list[0][0], call1)
126
127 # Filter with debug symbols file path.
128 gen_mock.reset_mock()
129 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
130 self.board, sysroot=self.board_dir, breakpad_dir=self.breakpad_dir,
131 file_list=[os.path.join(self.debug_dir, 'usr', 'sbin', 'elf.debug')])
132 self.assertEquals(ret, 0)
133 self.assertEquals(gen_mock.call_count, 1)
134 self.assertEquals(gen_mock.call_args_list[0][0], call1)
135
136
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400137 def testGenLimit(self, gen_mock):
138 """Verify generate_count arg works"""
139 with parallel_unittest.ParallelMock():
140 # Generate nothing!
141 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
142 self.board, sysroot=self.board_dir, breakpad_dir=self.breakpad_dir,
143 generate_count=0)
144 self.assertEquals(ret, 0)
145 self.assertEquals(gen_mock.call_count, 0)
146
147 # Generate just one.
148 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
149 self.board, sysroot=self.board_dir, breakpad_dir=self.breakpad_dir,
150 generate_count=1)
151 self.assertEquals(ret, 0)
152 self.assertEquals(gen_mock.call_count, 1)
153
154 # The largest ELF should be processed first.
155 call1 = (os.path.join(self.board_dir, 'iii/large-elf'),
156 os.path.join(self.debug_dir, 'iii/large-elf.debug'))
157 self.assertEquals(gen_mock.call_args_list[0][0], call1)
158
159 def testGenErrors(self, gen_mock):
160 """Verify we handle errors from generation correctly"""
Mike Frysingerc15efa52013-12-12 01:13:56 -0500161 def _SetError(*_args, **kwargs):
162 kwargs['num_errors'].value += 1
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400163 return 1
164 gen_mock.side_effect = _SetError
165 with parallel_unittest.ParallelMock():
166 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
167 self.board, sysroot=self.board_dir)
168 self.assertEquals(ret, 3)
169 self.assertEquals(gen_mock.call_count, 3)
170
Mike Frysinger9a628bb2013-10-24 15:51:37 -0400171 def testCleaningTrue(self, gen_mock):
172 """Verify behavior of clean_breakpad=True"""
173 with parallel_unittest.ParallelMock():
174 # Dir does not exist, and then does.
175 self.assertNotExists(self.breakpad_dir)
176 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
177 self.board, sysroot=self.board_dir, generate_count=1,
178 clean_breakpad=True)
179 self.assertEquals(ret, 0)
180 self.assertEquals(gen_mock.call_count, 1)
181 self.assertExists(self.breakpad_dir)
182
183 # Dir exists before & after.
184 # File exists, but then doesn't.
185 dummy_file = os.path.join(self.breakpad_dir, 'fooooooooo')
186 osutils.Touch(dummy_file)
187 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
188 self.board, sysroot=self.board_dir, generate_count=1,
189 clean_breakpad=True)
190 self.assertEquals(ret, 0)
191 self.assertEquals(gen_mock.call_count, 2)
192 self.assertNotExists(dummy_file)
193
194 def testCleaningFalse(self, gen_mock):
195 """Verify behavior of clean_breakpad=False"""
196 with parallel_unittest.ParallelMock():
197 # Dir does not exist, and then does.
198 self.assertNotExists(self.breakpad_dir)
199 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
200 self.board, sysroot=self.board_dir, generate_count=1,
201 clean_breakpad=False)
202 self.assertEquals(ret, 0)
203 self.assertEquals(gen_mock.call_count, 1)
204 self.assertExists(self.breakpad_dir)
205
206 # Dir exists before & after.
207 # File exists before & after.
208 dummy_file = os.path.join(self.breakpad_dir, 'fooooooooo')
209 osutils.Touch(dummy_file)
210 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
211 self.board, sysroot=self.board_dir, generate_count=1,
212 clean_breakpad=False)
213 self.assertEquals(ret, 0)
214 self.assertEquals(gen_mock.call_count, 2)
215 self.assertExists(dummy_file)
216
Shawn Nematbakhsh2c169cb2013-10-29 16:23:58 -0700217 def testExclusionList(self, gen_mock):
218 """Verify files in directories of the exclusion list are excluded"""
219 exclude_dirs = ['bin', 'usr', 'fake/dir/fake']
220 with parallel_unittest.ParallelMock():
221 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
222 self.board, sysroot=self.board_dir, exclude_dirs=exclude_dirs)
223 self.assertEquals(ret, 0)
224 self.assertEquals(gen_mock.call_count, 1)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400225
226class GenerateSymbolTest(cros_test_lib.MockTempDirTestCase):
Don Garrett25f309a2014-03-19 14:02:12 -0700227 """Test cros_generate_breakpad_symbols.GenerateBreakpadSymbol."""
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400228
229 def setUp(self):
230 self.elf_file = os.path.join(self.tempdir, 'elf')
231 osutils.Touch(self.elf_file)
232 self.debug_dir = os.path.join(self.tempdir, 'debug')
233 self.debug_file = os.path.join(self.debug_dir, 'elf.debug')
234 osutils.Touch(self.debug_file, makedirs=True)
235 # Not needed as the code itself should create it as needed.
236 self.breakpad_dir = os.path.join(self.debug_dir, 'breakpad')
237
238 self.rc_mock = self.StartPatcher(cros_build_lib_unittest.RunCommandMock())
239 self.rc_mock.SetDefaultCmdResult(output='MODULE OS CPU ID NAME')
240 self.assertCommandContains = self.rc_mock.assertCommandContains
241 self.sym_file = os.path.join(self.breakpad_dir, 'NAME/ID/NAME.sym')
242
243 self.StartPatcher(FindDebugDirMock(self.debug_dir))
244
245 def assertCommandArgs(self, i, args):
246 """Helper for looking at the args of the |i|th call"""
247 self.assertEqual(self.rc_mock.call_args_list[i][0][0], args)
248
249 def testNormal(self):
250 """Normal run -- given an ELF and a debug file"""
251 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
252 self.elf_file, self.debug_file, breakpad_dir=self.breakpad_dir)
253 self.assertEqual(ret, 0)
254 self.assertEqual(self.rc_mock.call_count, 1)
255 self.assertCommandArgs(0, ['dump_syms', self.elf_file, self.debug_dir])
256 self.assertExists(self.sym_file)
257
258 def testNormalBoard(self):
259 """Normal run w/board info but not breakpad dir"""
260 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
261 self.elf_file, board='foo')
262 self.assertEqual(ret, 0)
263 self.assertCommandArgs(0, ['dump_syms', self.elf_file])
264 self.assertEqual(self.rc_mock.call_count, 1)
265 self.assertExists(self.sym_file)
266
267 def testNormalNoCfi(self):
268 """Normal run w/out CFI"""
269 # Make sure the num_errors flag works too.
270 num_errors = ctypes.c_int()
271 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
272 self.elf_file, strip_cfi=True, num_errors=num_errors)
273 self.assertEqual(ret, 0)
274 self.assertEqual(num_errors.value, 0)
275 self.assertCommandArgs(0, ['dump_syms', '-c', self.elf_file])
276 self.assertEqual(self.rc_mock.call_count, 1)
277 self.assertExists(self.sym_file)
278
279 def testNormalElfOnly(self):
280 """Normal run -- given just an ELF"""
281 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(self.elf_file)
282 self.assertEqual(ret, 0)
283 self.assertCommandArgs(0, ['dump_syms', self.elf_file])
284 self.assertEqual(self.rc_mock.call_count, 1)
285 self.assertExists(self.sym_file)
286
287 def testNormalSudo(self):
288 """Normal run where ELF is readable only by root"""
289 with mock.patch.object(os, 'access') as mock_access:
290 mock_access.return_value = False
291 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(self.elf_file)
292 self.assertEqual(ret, 0)
293 self.assertCommandArgs(0, ['sudo', '--', 'dump_syms', self.elf_file])
294
295 def testLargeDebugFail(self):
296 """Running w/large .debug failed, but retry worked"""
297 self.rc_mock.AddCmdResult(['dump_syms', self.elf_file, self.debug_dir],
298 returncode=1)
299 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
300 self.elf_file, self.debug_file)
301 self.assertEqual(ret, 0)
302 self.assertEqual(self.rc_mock.call_count, 2)
303 self.assertCommandArgs(0, ['dump_syms', self.elf_file, self.debug_dir])
304 self.assertCommandArgs(
305 1, ['dump_syms', '-c', '-r', self.elf_file, self.debug_dir])
306 self.assertExists(self.sym_file)
307
308 def testDebugFail(self):
309 """Running w/.debug always failed, but works w/out"""
310 self.rc_mock.AddCmdResult(['dump_syms', self.elf_file, self.debug_dir],
311 returncode=1)
312 self.rc_mock.AddCmdResult(['dump_syms', '-c', '-r', self.elf_file,
313 self.debug_dir],
314 returncode=1)
315 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
316 self.elf_file, self.debug_file)
317 self.assertEqual(ret, 0)
318 self.assertEqual(self.rc_mock.call_count, 3)
319 self.assertCommandArgs(0, ['dump_syms', self.elf_file, self.debug_dir])
320 self.assertCommandArgs(
321 1, ['dump_syms', '-c', '-r', self.elf_file, self.debug_dir])
322 self.assertCommandArgs(2, ['dump_syms', self.elf_file])
323 self.assertExists(self.sym_file)
324
325 def testCompleteFail(self):
326 """Running dump_syms always fails"""
327 self.rc_mock.SetDefaultCmdResult(returncode=1)
328 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(self.elf_file)
329 self.assertEqual(ret, 1)
330 # Make sure the num_errors flag works too.
331 num_errors = ctypes.c_int()
332 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
333 self.elf_file, num_errors=num_errors)
334 self.assertEqual(ret, 1)
335 self.assertEqual(num_errors.value, 1)
336
337
338class UtilsTestDir(cros_test_lib.TempDirTestCase):
Don Garrett25f309a2014-03-19 14:02:12 -0700339 """Tests cros_generate_breakpad_symbols.ReadSymsHeader."""
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400340
341 def testReadSymsHeaderGoodFile(self):
342 """Make sure ReadSymsHeader can parse sym files"""
343 sym_file = os.path.join(self.tempdir, 'sym')
344 osutils.WriteFile(sym_file, 'MODULE Linux x86 s0m31D chrooome')
345 result = cros_generate_breakpad_symbols.ReadSymsHeader(sym_file)
346 self.assertEquals(result.cpu, 'x86')
347 self.assertEquals(result.id, 's0m31D')
348 self.assertEquals(result.name, 'chrooome')
349 self.assertEquals(result.os, 'Linux')
350
351
352class UtilsTest(cros_test_lib.TestCase):
Don Garrett25f309a2014-03-19 14:02:12 -0700353 """Tests cros_generate_breakpad_symbols.ReadSymsHeader."""
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400354
355 def testReadSymsHeaderGoodBuffer(self):
356 """Make sure ReadSymsHeader can parse sym file handles"""
357 result = cros_generate_breakpad_symbols.ReadSymsHeader(
358 StringIO.StringIO('MODULE Linux arm MY-ID-HERE blkid'))
359 self.assertEquals(result.cpu, 'arm')
360 self.assertEquals(result.id, 'MY-ID-HERE')
361 self.assertEquals(result.name, 'blkid')
362 self.assertEquals(result.os, 'Linux')
363
364 def testReadSymsHeaderBadd(self):
365 """Make sure ReadSymsHeader throws on bad sym files"""
366 self.assertRaises(ValueError, cros_generate_breakpad_symbols.ReadSymsHeader,
367 StringIO.StringIO('asdf'))
368
369 def testBreakpadDir(self):
370 """Make sure board->breakpad path expansion works"""
371 expected = '/build/blah/usr/lib/debug/breakpad'
372 result = cros_generate_breakpad_symbols.FindBreakpadDir('blah')
373 self.assertEquals(expected, result)
374
375 def testDebugDir(self):
376 """Make sure board->debug path expansion works"""
377 expected = '/build/blah/usr/lib/debug'
378 result = cros_generate_breakpad_symbols.FindDebugDir('blah')
379 self.assertEquals(expected, result)
380
381
382if __name__ == '__main__':
383 # pylint: disable=W0212
384 # Set timeouts small so that if the unit test hangs, it won't hang for long.
385 parallel._BackgroundTask.STARTUP_TIMEOUT = 5
386 parallel._BackgroundTask.EXIT_TIMEOUT = 5
387
388 # Run the tests.
389 cros_test_lib.main(level=logging.INFO)