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