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