blob: 2756409d73dd43099d4c110cdb194a54461caa6c [file] [log] [blame]
Mike Frysinger69cb41d2013-08-11 20:08:19 -04001# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Don Garrett25f309a2014-03-19 14:02:12 -07005"""Test cros_generate_breakpad_symbols."""
6
Mike Frysinger383367e2014-09-16 15:06:17 -04007from __future__ import print_function
8
Mike Frysinger69cb41d2013-08-11 20:08:19 -04009import ctypes
Mike Frysingerea838d12014-12-08 11:55:32 -050010import mock
Mike Frysinger69cb41d2013-08-11 20:08:19 -040011import os
12import StringIO
Mike Frysinger69cb41d2013-08-11 20:08:19 -040013
Mike Frysinger69cb41d2013-08-11 20:08:19 -040014from chromite.lib import cros_build_lib_unittest
15from chromite.lib import cros_test_lib
16from chromite.lib import osutils
17from chromite.lib import parallel
18from chromite.lib import parallel_unittest
19from chromite.lib import partial_mock
20from chromite.scripts import cros_generate_breakpad_symbols
21
Mike Frysinger69cb41d2013-08-11 20:08:19 -040022
23class FindDebugDirMock(partial_mock.PartialMock):
24 """Mock out the DebugDir helper so we can point it to a tempdir."""
25
26 TARGET = 'chromite.scripts.cros_generate_breakpad_symbols'
27 ATTRS = ('FindDebugDir',)
28 DEFAULT_ATTR = 'FindDebugDir'
29
Mike Frysingerc15efa52013-12-12 01:13:56 -050030 def __init__(self, path, *args, **kwargs):
Mike Frysinger69cb41d2013-08-11 20:08:19 -040031 self.path = path
Mike Frysingerc15efa52013-12-12 01:13:56 -050032 super(FindDebugDirMock, self).__init__(*args, **kwargs)
Mike Frysinger69cb41d2013-08-11 20:08:19 -040033
34 def FindDebugDir(self, _board):
35 return self.path
36
37
38@mock.patch('chromite.scripts.cros_generate_breakpad_symbols.'
39 'GenerateBreakpadSymbol')
40class GenerateSymbolsTest(cros_test_lib.MockTempDirTestCase):
Don Garrettf8bf7842014-03-20 17:03:42 -070041 """Test GenerateBreakpadSymbols."""
Mike Frysinger69cb41d2013-08-11 20:08:19 -040042
43 def setUp(self):
44 self.board = 'monkey-board'
45 self.board_dir = os.path.join(self.tempdir, 'build', self.board)
46 self.debug_dir = os.path.join(self.board_dir, 'usr', 'lib', 'debug')
47 self.breakpad_dir = os.path.join(self.debug_dir, 'breakpad')
48
49 # Generate a tree of files which we'll scan through.
50 elf_files = [
51 'bin/elf',
52 'iii/large-elf',
53 # Need some kernel modules (with & without matching .debug).
54 'lib/modules/3.10/module.ko',
55 'lib/modules/3.10/module-no-debug.ko',
56 # Need a file which has an ELF only, but not a .debug.
57 'usr/bin/elf-only',
58 'usr/sbin/elf',
59 ]
60 debug_files = [
61 'bin/bad-file',
62 'bin/elf.debug',
63 'iii/large-elf.debug',
64 'lib/modules/3.10/module.ko.debug',
65 # Need a file which has a .debug only, but not an ELF.
66 'sbin/debug-only.debug',
67 'usr/sbin/elf.debug',
68 ]
69 for f in ([os.path.join(self.board_dir, x) for x in elf_files] +
70 [os.path.join(self.debug_dir, x) for x in debug_files]):
71 osutils.Touch(f, makedirs=True)
72
73 # Set up random build dirs and symlinks.
74 buildid = os.path.join(self.debug_dir, '.build-id', '00')
75 osutils.SafeMakedirs(buildid)
76 os.symlink('/asdf', os.path.join(buildid, 'foo'))
77 os.symlink('/bin/sh', os.path.join(buildid, 'foo.debug'))
78 os.symlink('/bin/sh', os.path.join(self.debug_dir, 'file.debug'))
79 osutils.WriteFile(os.path.join(self.debug_dir, 'iii', 'large-elf.debug'),
80 'just some content')
81
82 self.StartPatcher(FindDebugDirMock(self.debug_dir))
83
84 def testNormal(self, gen_mock):
85 """Verify all the files we expect to get generated do"""
86 with parallel_unittest.ParallelMock():
87 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
88 self.board, sysroot=self.board_dir)
89 self.assertEquals(ret, 0)
90 self.assertEquals(gen_mock.call_count, 3)
91
92 # The largest ELF should be processed first.
93 call1 = (os.path.join(self.board_dir, 'iii/large-elf'),
94 os.path.join(self.debug_dir, 'iii/large-elf.debug'))
95 self.assertEquals(gen_mock.call_args_list[0][0], call1)
96
97 # The other ELFs can be called in any order.
98 call2 = (os.path.join(self.board_dir, 'bin/elf'),
99 os.path.join(self.debug_dir, 'bin/elf.debug'))
100 call3 = (os.path.join(self.board_dir, 'usr/sbin/elf'),
101 os.path.join(self.debug_dir, 'usr/sbin/elf.debug'))
102 exp_calls = set((call2, call3))
103 actual_calls = set((gen_mock.call_args_list[1][0],
104 gen_mock.call_args_list[2][0]))
105 self.assertEquals(exp_calls, actual_calls)
106
Prathmesh Prabhu9995e9b2013-10-31 16:43:55 -0700107 def testFileList(self, gen_mock):
108 """Verify that file_list restricts the symbols generated"""
109 with parallel_unittest.ParallelMock():
110 call1 = (os.path.join(self.board_dir, 'usr/sbin/elf'),
111 os.path.join(self.debug_dir, 'usr/sbin/elf.debug'))
112
113 # Filter with elf path.
114 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
115 self.board, sysroot=self.board_dir, breakpad_dir=self.breakpad_dir,
116 file_list=[os.path.join(self.board_dir, 'usr', 'sbin', 'elf')])
117 self.assertEquals(ret, 0)
118 self.assertEquals(gen_mock.call_count, 1)
119 self.assertEquals(gen_mock.call_args_list[0][0], call1)
120
121 # Filter with debug symbols file path.
122 gen_mock.reset_mock()
123 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
124 self.board, sysroot=self.board_dir, breakpad_dir=self.breakpad_dir,
125 file_list=[os.path.join(self.debug_dir, 'usr', 'sbin', 'elf.debug')])
126 self.assertEquals(ret, 0)
127 self.assertEquals(gen_mock.call_count, 1)
128 self.assertEquals(gen_mock.call_args_list[0][0], call1)
129
130
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400131 def testGenLimit(self, gen_mock):
132 """Verify generate_count arg works"""
133 with parallel_unittest.ParallelMock():
134 # Generate nothing!
135 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
136 self.board, sysroot=self.board_dir, breakpad_dir=self.breakpad_dir,
137 generate_count=0)
138 self.assertEquals(ret, 0)
139 self.assertEquals(gen_mock.call_count, 0)
140
141 # Generate just one.
142 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
143 self.board, sysroot=self.board_dir, breakpad_dir=self.breakpad_dir,
144 generate_count=1)
145 self.assertEquals(ret, 0)
146 self.assertEquals(gen_mock.call_count, 1)
147
148 # The largest ELF should be processed first.
149 call1 = (os.path.join(self.board_dir, 'iii/large-elf'),
150 os.path.join(self.debug_dir, 'iii/large-elf.debug'))
151 self.assertEquals(gen_mock.call_args_list[0][0], call1)
152
153 def testGenErrors(self, gen_mock):
154 """Verify we handle errors from generation correctly"""
Mike Frysingerc15efa52013-12-12 01:13:56 -0500155 def _SetError(*_args, **kwargs):
156 kwargs['num_errors'].value += 1
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400157 return 1
158 gen_mock.side_effect = _SetError
159 with parallel_unittest.ParallelMock():
160 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
161 self.board, sysroot=self.board_dir)
162 self.assertEquals(ret, 3)
163 self.assertEquals(gen_mock.call_count, 3)
164
Mike Frysinger9a628bb2013-10-24 15:51:37 -0400165 def testCleaningTrue(self, gen_mock):
166 """Verify behavior of clean_breakpad=True"""
167 with parallel_unittest.ParallelMock():
168 # Dir does not exist, and then does.
169 self.assertNotExists(self.breakpad_dir)
170 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
171 self.board, sysroot=self.board_dir, generate_count=1,
172 clean_breakpad=True)
173 self.assertEquals(ret, 0)
174 self.assertEquals(gen_mock.call_count, 1)
175 self.assertExists(self.breakpad_dir)
176
177 # Dir exists before & after.
178 # File exists, but then doesn't.
179 dummy_file = os.path.join(self.breakpad_dir, 'fooooooooo')
180 osutils.Touch(dummy_file)
181 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
182 self.board, sysroot=self.board_dir, generate_count=1,
183 clean_breakpad=True)
184 self.assertEquals(ret, 0)
185 self.assertEquals(gen_mock.call_count, 2)
186 self.assertNotExists(dummy_file)
187
188 def testCleaningFalse(self, gen_mock):
189 """Verify behavior of clean_breakpad=False"""
190 with parallel_unittest.ParallelMock():
191 # Dir does not exist, and then does.
192 self.assertNotExists(self.breakpad_dir)
193 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
194 self.board, sysroot=self.board_dir, generate_count=1,
195 clean_breakpad=False)
196 self.assertEquals(ret, 0)
197 self.assertEquals(gen_mock.call_count, 1)
198 self.assertExists(self.breakpad_dir)
199
200 # Dir exists before & after.
201 # File exists before & after.
202 dummy_file = os.path.join(self.breakpad_dir, 'fooooooooo')
203 osutils.Touch(dummy_file)
204 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
205 self.board, sysroot=self.board_dir, generate_count=1,
206 clean_breakpad=False)
207 self.assertEquals(ret, 0)
208 self.assertEquals(gen_mock.call_count, 2)
209 self.assertExists(dummy_file)
210
Shawn Nematbakhsh2c169cb2013-10-29 16:23:58 -0700211 def testExclusionList(self, gen_mock):
212 """Verify files in directories of the exclusion list are excluded"""
213 exclude_dirs = ['bin', 'usr', 'fake/dir/fake']
214 with parallel_unittest.ParallelMock():
215 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
216 self.board, sysroot=self.board_dir, exclude_dirs=exclude_dirs)
217 self.assertEquals(ret, 0)
218 self.assertEquals(gen_mock.call_count, 1)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400219
220class GenerateSymbolTest(cros_test_lib.MockTempDirTestCase):
Don Garrettf8bf7842014-03-20 17:03:42 -0700221 """Test GenerateBreakpadSymbol."""
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400222
223 def setUp(self):
224 self.elf_file = os.path.join(self.tempdir, 'elf')
225 osutils.Touch(self.elf_file)
226 self.debug_dir = os.path.join(self.tempdir, 'debug')
227 self.debug_file = os.path.join(self.debug_dir, 'elf.debug')
228 osutils.Touch(self.debug_file, makedirs=True)
229 # Not needed as the code itself should create it as needed.
230 self.breakpad_dir = os.path.join(self.debug_dir, 'breakpad')
231
232 self.rc_mock = self.StartPatcher(cros_build_lib_unittest.RunCommandMock())
233 self.rc_mock.SetDefaultCmdResult(output='MODULE OS CPU ID NAME')
234 self.assertCommandContains = self.rc_mock.assertCommandContains
235 self.sym_file = os.path.join(self.breakpad_dir, 'NAME/ID/NAME.sym')
236
237 self.StartPatcher(FindDebugDirMock(self.debug_dir))
238
239 def assertCommandArgs(self, i, args):
240 """Helper for looking at the args of the |i|th call"""
241 self.assertEqual(self.rc_mock.call_args_list[i][0][0], args)
242
243 def testNormal(self):
244 """Normal run -- given an ELF and a debug file"""
245 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
Don Garrette548cff2015-09-23 14:36:21 -0700246 self.elf_file, self.debug_file, self.breakpad_dir)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400247 self.assertEqual(ret, 0)
248 self.assertEqual(self.rc_mock.call_count, 1)
249 self.assertCommandArgs(0, ['dump_syms', self.elf_file, self.debug_dir])
250 self.assertExists(self.sym_file)
251
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400252 def testNormalNoCfi(self):
253 """Normal run w/out CFI"""
254 # Make sure the num_errors flag works too.
255 num_errors = ctypes.c_int()
256 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
Don Garrette548cff2015-09-23 14:36:21 -0700257 self.elf_file, breakpad_dir=self.breakpad_dir,
258 strip_cfi=True, num_errors=num_errors)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400259 self.assertEqual(ret, 0)
260 self.assertEqual(num_errors.value, 0)
261 self.assertCommandArgs(0, ['dump_syms', '-c', self.elf_file])
262 self.assertEqual(self.rc_mock.call_count, 1)
263 self.assertExists(self.sym_file)
264
265 def testNormalElfOnly(self):
266 """Normal run -- given just an ELF"""
Don Garrette548cff2015-09-23 14:36:21 -0700267 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
268 self.elf_file, breakpad_dir=self.breakpad_dir)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400269 self.assertEqual(ret, 0)
270 self.assertCommandArgs(0, ['dump_syms', self.elf_file])
271 self.assertEqual(self.rc_mock.call_count, 1)
272 self.assertExists(self.sym_file)
273
274 def testNormalSudo(self):
275 """Normal run where ELF is readable only by root"""
276 with mock.patch.object(os, 'access') as mock_access:
277 mock_access.return_value = False
Don Garrette548cff2015-09-23 14:36:21 -0700278 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
279 self.elf_file, breakpad_dir=self.breakpad_dir)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400280 self.assertEqual(ret, 0)
281 self.assertCommandArgs(0, ['sudo', '--', 'dump_syms', self.elf_file])
282
283 def testLargeDebugFail(self):
284 """Running w/large .debug failed, but retry worked"""
285 self.rc_mock.AddCmdResult(['dump_syms', self.elf_file, self.debug_dir],
286 returncode=1)
287 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
Don Garrette548cff2015-09-23 14:36:21 -0700288 self.elf_file, self.debug_file, self.breakpad_dir)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400289 self.assertEqual(ret, 0)
290 self.assertEqual(self.rc_mock.call_count, 2)
291 self.assertCommandArgs(0, ['dump_syms', self.elf_file, self.debug_dir])
292 self.assertCommandArgs(
293 1, ['dump_syms', '-c', '-r', self.elf_file, self.debug_dir])
294 self.assertExists(self.sym_file)
295
296 def testDebugFail(self):
297 """Running w/.debug always failed, but works w/out"""
298 self.rc_mock.AddCmdResult(['dump_syms', self.elf_file, self.debug_dir],
299 returncode=1)
300 self.rc_mock.AddCmdResult(['dump_syms', '-c', '-r', self.elf_file,
301 self.debug_dir],
302 returncode=1)
303 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
Don Garrette548cff2015-09-23 14:36:21 -0700304 self.elf_file, self.debug_file, self.breakpad_dir)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400305 self.assertEqual(ret, 0)
306 self.assertEqual(self.rc_mock.call_count, 3)
307 self.assertCommandArgs(0, ['dump_syms', self.elf_file, self.debug_dir])
308 self.assertCommandArgs(
309 1, ['dump_syms', '-c', '-r', self.elf_file, self.debug_dir])
310 self.assertCommandArgs(2, ['dump_syms', self.elf_file])
311 self.assertExists(self.sym_file)
312
313 def testCompleteFail(self):
314 """Running dump_syms always fails"""
315 self.rc_mock.SetDefaultCmdResult(returncode=1)
Don Garrette548cff2015-09-23 14:36:21 -0700316 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
317 self.elf_file, breakpad_dir=self.breakpad_dir)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400318 self.assertEqual(ret, 1)
319 # Make sure the num_errors flag works too.
320 num_errors = ctypes.c_int()
321 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
Don Garrette548cff2015-09-23 14:36:21 -0700322 self.elf_file, breakpad_dir=self.breakpad_dir, num_errors=num_errors)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400323 self.assertEqual(ret, 1)
324 self.assertEqual(num_errors.value, 1)
325
326
327class UtilsTestDir(cros_test_lib.TempDirTestCase):
Don Garrettf8bf7842014-03-20 17:03:42 -0700328 """Tests ReadSymsHeader."""
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400329
330 def testReadSymsHeaderGoodFile(self):
331 """Make sure ReadSymsHeader can parse sym files"""
332 sym_file = os.path.join(self.tempdir, 'sym')
333 osutils.WriteFile(sym_file, 'MODULE Linux x86 s0m31D chrooome')
334 result = cros_generate_breakpad_symbols.ReadSymsHeader(sym_file)
335 self.assertEquals(result.cpu, 'x86')
336 self.assertEquals(result.id, 's0m31D')
337 self.assertEquals(result.name, 'chrooome')
338 self.assertEquals(result.os, 'Linux')
339
340
341class UtilsTest(cros_test_lib.TestCase):
Don Garrettf8bf7842014-03-20 17:03:42 -0700342 """Tests ReadSymsHeader."""
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400343
344 def testReadSymsHeaderGoodBuffer(self):
345 """Make sure ReadSymsHeader can parse sym file handles"""
346 result = cros_generate_breakpad_symbols.ReadSymsHeader(
347 StringIO.StringIO('MODULE Linux arm MY-ID-HERE blkid'))
348 self.assertEquals(result.cpu, 'arm')
349 self.assertEquals(result.id, 'MY-ID-HERE')
350 self.assertEquals(result.name, 'blkid')
351 self.assertEquals(result.os, 'Linux')
352
353 def testReadSymsHeaderBadd(self):
354 """Make sure ReadSymsHeader throws on bad sym files"""
355 self.assertRaises(ValueError, cros_generate_breakpad_symbols.ReadSymsHeader,
356 StringIO.StringIO('asdf'))
357
358 def testBreakpadDir(self):
359 """Make sure board->breakpad path expansion works"""
360 expected = '/build/blah/usr/lib/debug/breakpad'
361 result = cros_generate_breakpad_symbols.FindBreakpadDir('blah')
362 self.assertEquals(expected, result)
363
364 def testDebugDir(self):
365 """Make sure board->debug path expansion works"""
366 expected = '/build/blah/usr/lib/debug'
367 result = cros_generate_breakpad_symbols.FindDebugDir('blah')
368 self.assertEquals(expected, result)
369
370
Mike Frysingerea838d12014-12-08 11:55:32 -0500371def main(_argv):
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400372 # pylint: disable=W0212
373 # Set timeouts small so that if the unit test hangs, it won't hang for long.
374 parallel._BackgroundTask.STARTUP_TIMEOUT = 5
375 parallel._BackgroundTask.EXIT_TIMEOUT = 5
376
377 # Run the tests.
Mike Frysingerba167372015-01-21 10:37:03 -0500378 cros_test_lib.main(level='info', module=__name__)