blob: 38fe67310a3575f4a8e52a50b5ae5063347b0315 [file] [log] [blame]
Mike Frysingere58c0e22017-10-04 15:43:30 -04001# -*- coding: utf-8 -*-
Mike Frysinger69cb41d2013-08-11 20:08:19 -04002# 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 Frysinger383367e2014-09-16 15:06:17 -04008from __future__ import print_function
9
Mike Frysinger69cb41d2013-08-11 20:08:19 -040010import ctypes
Mike Frysinger69cb41d2013-08-11 20:08:19 -040011import os
12import StringIO
Mike Frysinger69cb41d2013-08-11 20:08:19 -040013
Mike Frysinger6db648e2018-07-24 19:57:58 -040014import mock
15
Mike Frysinger69cb41d2013-08-11 20:08:19 -040016from 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
Mike Frysinger3f571af2016-08-31 23:56:53 -040035 # pylint: disable=unused-argument
36 def FindDebugDir(self, _board, sysroot=None):
Mike Frysinger69cb41d2013-08-11 20:08:19 -040037 return self.path
38
39
40@mock.patch('chromite.scripts.cros_generate_breakpad_symbols.'
41 'GenerateBreakpadSymbol')
42class GenerateSymbolsTest(cros_test_lib.MockTempDirTestCase):
Don Garrettf8bf7842014-03-20 17:03:42 -070043 """Test GenerateBreakpadSymbols."""
Mike Frysinger69cb41d2013-08-11 20:08:19 -040044
45 def setUp(self):
46 self.board = 'monkey-board'
47 self.board_dir = os.path.join(self.tempdir, 'build', self.board)
48 self.debug_dir = os.path.join(self.board_dir, 'usr', 'lib', 'debug')
49 self.breakpad_dir = os.path.join(self.debug_dir, 'breakpad')
50
51 # Generate a tree of files which we'll scan through.
52 elf_files = [
53 'bin/elf',
54 'iii/large-elf',
55 # Need some kernel modules (with & without matching .debug).
56 'lib/modules/3.10/module.ko',
57 'lib/modules/3.10/module-no-debug.ko',
58 # Need a file which has an ELF only, but not a .debug.
59 'usr/bin/elf-only',
60 'usr/sbin/elf',
61 ]
62 debug_files = [
63 'bin/bad-file',
64 'bin/elf.debug',
65 'iii/large-elf.debug',
66 'lib/modules/3.10/module.ko.debug',
67 # Need a file which has a .debug only, but not an ELF.
68 'sbin/debug-only.debug',
69 'usr/sbin/elf.debug',
70 ]
71 for f in ([os.path.join(self.board_dir, x) for x in elf_files] +
72 [os.path.join(self.debug_dir, x) for x in debug_files]):
73 osutils.Touch(f, makedirs=True)
74
75 # Set up random build dirs and symlinks.
76 buildid = os.path.join(self.debug_dir, '.build-id', '00')
77 osutils.SafeMakedirs(buildid)
78 os.symlink('/asdf', os.path.join(buildid, 'foo'))
79 os.symlink('/bin/sh', os.path.join(buildid, 'foo.debug'))
80 os.symlink('/bin/sh', os.path.join(self.debug_dir, 'file.debug'))
81 osutils.WriteFile(os.path.join(self.debug_dir, 'iii', 'large-elf.debug'),
82 'just some content')
83
84 self.StartPatcher(FindDebugDirMock(self.debug_dir))
85
86 def testNormal(self, gen_mock):
87 """Verify all the files we expect to get generated do"""
88 with parallel_unittest.ParallelMock():
89 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
90 self.board, sysroot=self.board_dir)
91 self.assertEquals(ret, 0)
92 self.assertEquals(gen_mock.call_count, 3)
93
94 # The largest ELF should be processed first.
95 call1 = (os.path.join(self.board_dir, 'iii/large-elf'),
96 os.path.join(self.debug_dir, 'iii/large-elf.debug'))
97 self.assertEquals(gen_mock.call_args_list[0][0], call1)
98
99 # The other ELFs can be called in any order.
100 call2 = (os.path.join(self.board_dir, 'bin/elf'),
101 os.path.join(self.debug_dir, 'bin/elf.debug'))
102 call3 = (os.path.join(self.board_dir, 'usr/sbin/elf'),
103 os.path.join(self.debug_dir, 'usr/sbin/elf.debug'))
104 exp_calls = set((call2, call3))
105 actual_calls = set((gen_mock.call_args_list[1][0],
106 gen_mock.call_args_list[2][0]))
107 self.assertEquals(exp_calls, actual_calls)
108
Prathmesh Prabhu9995e9b2013-10-31 16:43:55 -0700109 def testFileList(self, gen_mock):
110 """Verify that file_list restricts the symbols generated"""
111 with parallel_unittest.ParallelMock():
112 call1 = (os.path.join(self.board_dir, 'usr/sbin/elf'),
113 os.path.join(self.debug_dir, 'usr/sbin/elf.debug'))
114
115 # Filter with elf path.
116 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
117 self.board, sysroot=self.board_dir, breakpad_dir=self.breakpad_dir,
118 file_list=[os.path.join(self.board_dir, 'usr', 'sbin', 'elf')])
119 self.assertEquals(ret, 0)
120 self.assertEquals(gen_mock.call_count, 1)
121 self.assertEquals(gen_mock.call_args_list[0][0], call1)
122
123 # Filter with debug symbols file path.
124 gen_mock.reset_mock()
125 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
126 self.board, sysroot=self.board_dir, breakpad_dir=self.breakpad_dir,
127 file_list=[os.path.join(self.debug_dir, 'usr', 'sbin', 'elf.debug')])
128 self.assertEquals(ret, 0)
129 self.assertEquals(gen_mock.call_count, 1)
130 self.assertEquals(gen_mock.call_args_list[0][0], call1)
131
132
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400133 def testGenLimit(self, gen_mock):
134 """Verify generate_count arg works"""
135 with parallel_unittest.ParallelMock():
136 # Generate nothing!
137 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
138 self.board, sysroot=self.board_dir, breakpad_dir=self.breakpad_dir,
139 generate_count=0)
140 self.assertEquals(ret, 0)
141 self.assertEquals(gen_mock.call_count, 0)
142
143 # Generate just one.
144 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
145 self.board, sysroot=self.board_dir, breakpad_dir=self.breakpad_dir,
146 generate_count=1)
147 self.assertEquals(ret, 0)
148 self.assertEquals(gen_mock.call_count, 1)
149
150 # The largest ELF should be processed first.
151 call1 = (os.path.join(self.board_dir, 'iii/large-elf'),
152 os.path.join(self.debug_dir, 'iii/large-elf.debug'))
153 self.assertEquals(gen_mock.call_args_list[0][0], call1)
154
155 def testGenErrors(self, gen_mock):
156 """Verify we handle errors from generation correctly"""
Mike Frysingerc15efa52013-12-12 01:13:56 -0500157 def _SetError(*_args, **kwargs):
158 kwargs['num_errors'].value += 1
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400159 return 1
160 gen_mock.side_effect = _SetError
161 with parallel_unittest.ParallelMock():
162 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
163 self.board, sysroot=self.board_dir)
164 self.assertEquals(ret, 3)
165 self.assertEquals(gen_mock.call_count, 3)
166
Mike Frysinger9a628bb2013-10-24 15:51:37 -0400167 def testCleaningTrue(self, gen_mock):
168 """Verify behavior of clean_breakpad=True"""
169 with parallel_unittest.ParallelMock():
170 # Dir does not exist, and then does.
171 self.assertNotExists(self.breakpad_dir)
172 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
173 self.board, sysroot=self.board_dir, generate_count=1,
174 clean_breakpad=True)
175 self.assertEquals(ret, 0)
176 self.assertEquals(gen_mock.call_count, 1)
177 self.assertExists(self.breakpad_dir)
178
179 # Dir exists before & after.
180 # File exists, but then doesn't.
181 dummy_file = os.path.join(self.breakpad_dir, 'fooooooooo')
182 osutils.Touch(dummy_file)
183 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
184 self.board, sysroot=self.board_dir, generate_count=1,
185 clean_breakpad=True)
186 self.assertEquals(ret, 0)
187 self.assertEquals(gen_mock.call_count, 2)
188 self.assertNotExists(dummy_file)
189
190 def testCleaningFalse(self, gen_mock):
191 """Verify behavior of clean_breakpad=False"""
192 with parallel_unittest.ParallelMock():
193 # Dir does not exist, and then does.
194 self.assertNotExists(self.breakpad_dir)
195 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
196 self.board, sysroot=self.board_dir, generate_count=1,
197 clean_breakpad=False)
198 self.assertEquals(ret, 0)
199 self.assertEquals(gen_mock.call_count, 1)
200 self.assertExists(self.breakpad_dir)
201
202 # Dir exists before & after.
203 # File exists before & after.
204 dummy_file = os.path.join(self.breakpad_dir, 'fooooooooo')
205 osutils.Touch(dummy_file)
206 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
207 self.board, sysroot=self.board_dir, generate_count=1,
208 clean_breakpad=False)
209 self.assertEquals(ret, 0)
210 self.assertEquals(gen_mock.call_count, 2)
211 self.assertExists(dummy_file)
212
Shawn Nematbakhsh2c169cb2013-10-29 16:23:58 -0700213 def testExclusionList(self, gen_mock):
214 """Verify files in directories of the exclusion list are excluded"""
215 exclude_dirs = ['bin', 'usr', 'fake/dir/fake']
216 with parallel_unittest.ParallelMock():
217 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
218 self.board, sysroot=self.board_dir, exclude_dirs=exclude_dirs)
219 self.assertEquals(ret, 0)
220 self.assertEquals(gen_mock.call_count, 1)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400221
Benjamin Gordon121a2aa2018-05-04 16:24:45 -0600222class GenerateSymbolTest(cros_test_lib.RunCommandTempDirTestCase):
Don Garrettf8bf7842014-03-20 17:03:42 -0700223 """Test GenerateBreakpadSymbol."""
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400224
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
Don Garrett1d1209d2017-02-28 13:42:57 -0800234 self.rc.SetDefaultCmdResult(output='MODULE OS CPU ID NAME')
235 self.assertCommandContains = self.rc.assertCommandContains
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400236 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"""
Don Garrett1d1209d2017-02-28 13:42:57 -0800242 self.assertEqual(self.rc.call_args_list[i][0][0], args)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400243
244 def testNormal(self):
245 """Normal run -- given an ELF and a debug file"""
246 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
Don Garrette548cff2015-09-23 14:36:21 -0700247 self.elf_file, self.debug_file, self.breakpad_dir)
Don Garrettc9de3ac2015-10-01 15:40:10 -0700248 self.assertEqual(ret, self.sym_file)
Don Garrett1d1209d2017-02-28 13:42:57 -0800249 self.assertEqual(self.rc.call_count, 1)
Mike Frysinger2808deb2016-01-28 01:22:13 -0500250 self.assertCommandArgs(0, ['dump_syms', '-v', self.elf_file,
251 self.debug_dir])
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400252 self.assertExists(self.sym_file)
253
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400254 def testNormalNoCfi(self):
255 """Normal run w/out CFI"""
256 # Make sure the num_errors flag works too.
257 num_errors = ctypes.c_int()
258 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
Don Garrette548cff2015-09-23 14:36:21 -0700259 self.elf_file, breakpad_dir=self.breakpad_dir,
260 strip_cfi=True, num_errors=num_errors)
Don Garrettc9de3ac2015-10-01 15:40:10 -0700261 self.assertEqual(ret, self.sym_file)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400262 self.assertEqual(num_errors.value, 0)
Mike Frysinger2808deb2016-01-28 01:22:13 -0500263 self.assertCommandArgs(0, ['dump_syms', '-v', '-c', self.elf_file])
Don Garrett1d1209d2017-02-28 13:42:57 -0800264 self.assertEqual(self.rc.call_count, 1)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400265 self.assertExists(self.sym_file)
266
267 def testNormalElfOnly(self):
268 """Normal run -- given just an ELF"""
Don Garrette548cff2015-09-23 14:36:21 -0700269 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
270 self.elf_file, breakpad_dir=self.breakpad_dir)
Don Garrettc9de3ac2015-10-01 15:40:10 -0700271 self.assertEqual(ret, self.sym_file)
Mike Frysinger2808deb2016-01-28 01:22:13 -0500272 self.assertCommandArgs(0, ['dump_syms', '-v', self.elf_file])
Don Garrett1d1209d2017-02-28 13:42:57 -0800273 self.assertEqual(self.rc.call_count, 1)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400274 self.assertExists(self.sym_file)
275
276 def testNormalSudo(self):
277 """Normal run where ELF is readable only by root"""
278 with mock.patch.object(os, 'access') as mock_access:
279 mock_access.return_value = False
Don Garrette548cff2015-09-23 14:36:21 -0700280 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
281 self.elf_file, breakpad_dir=self.breakpad_dir)
Don Garrettc9de3ac2015-10-01 15:40:10 -0700282 self.assertEqual(ret, self.sym_file)
Mike Frysinger2808deb2016-01-28 01:22:13 -0500283 self.assertCommandArgs(0, ['sudo', '--', 'dump_syms', '-v', self.elf_file])
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400284
285 def testLargeDebugFail(self):
286 """Running w/large .debug failed, but retry worked"""
Don Garrett1d1209d2017-02-28 13:42:57 -0800287 self.rc.AddCmdResult(['dump_syms', '-v', self.elf_file, self.debug_dir],
288 returncode=1)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400289 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
Don Garrette548cff2015-09-23 14:36:21 -0700290 self.elf_file, self.debug_file, self.breakpad_dir)
Don Garrettc9de3ac2015-10-01 15:40:10 -0700291 self.assertEqual(ret, self.sym_file)
Don Garrett1d1209d2017-02-28 13:42:57 -0800292 self.assertEqual(self.rc.call_count, 2)
Mike Frysinger2808deb2016-01-28 01:22:13 -0500293 self.assertCommandArgs(0, ['dump_syms', '-v', self.elf_file,
294 self.debug_dir])
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400295 self.assertCommandArgs(
Mike Frysinger2808deb2016-01-28 01:22:13 -0500296 1, ['dump_syms', '-v', '-c', '-r', self.elf_file, self.debug_dir])
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400297 self.assertExists(self.sym_file)
298
299 def testDebugFail(self):
300 """Running w/.debug always failed, but works w/out"""
Don Garrett1d1209d2017-02-28 13:42:57 -0800301 self.rc.AddCmdResult(['dump_syms', '-v', self.elf_file, self.debug_dir],
302 returncode=1)
303 self.rc.AddCmdResult(['dump_syms', '-v', '-c', '-r', self.elf_file,
304 self.debug_dir],
305 returncode=1)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400306 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
Don Garrette548cff2015-09-23 14:36:21 -0700307 self.elf_file, self.debug_file, self.breakpad_dir)
Don Garrettc9de3ac2015-10-01 15:40:10 -0700308 self.assertEqual(ret, self.sym_file)
Don Garrett1d1209d2017-02-28 13:42:57 -0800309 self.assertEqual(self.rc.call_count, 3)
Mike Frysinger2808deb2016-01-28 01:22:13 -0500310 self.assertCommandArgs(0, ['dump_syms', '-v', self.elf_file,
311 self.debug_dir])
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400312 self.assertCommandArgs(
Mike Frysinger2808deb2016-01-28 01:22:13 -0500313 1, ['dump_syms', '-v', '-c', '-r', self.elf_file, self.debug_dir])
314 self.assertCommandArgs(2, ['dump_syms', '-v', self.elf_file])
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400315 self.assertExists(self.sym_file)
316
317 def testCompleteFail(self):
318 """Running dump_syms always fails"""
Don Garrett1d1209d2017-02-28 13:42:57 -0800319 self.rc.SetDefaultCmdResult(returncode=1)
Don Garrette548cff2015-09-23 14:36:21 -0700320 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
321 self.elf_file, breakpad_dir=self.breakpad_dir)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400322 self.assertEqual(ret, 1)
323 # Make sure the num_errors flag works too.
324 num_errors = ctypes.c_int()
325 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
Don Garrette548cff2015-09-23 14:36:21 -0700326 self.elf_file, breakpad_dir=self.breakpad_dir, num_errors=num_errors)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400327 self.assertEqual(ret, 1)
328 self.assertEqual(num_errors.value, 1)
329
330
331class UtilsTestDir(cros_test_lib.TempDirTestCase):
Don Garrettf8bf7842014-03-20 17:03:42 -0700332 """Tests ReadSymsHeader."""
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400333
334 def testReadSymsHeaderGoodFile(self):
335 """Make sure ReadSymsHeader can parse sym files"""
336 sym_file = os.path.join(self.tempdir, 'sym')
337 osutils.WriteFile(sym_file, 'MODULE Linux x86 s0m31D chrooome')
338 result = cros_generate_breakpad_symbols.ReadSymsHeader(sym_file)
339 self.assertEquals(result.cpu, 'x86')
340 self.assertEquals(result.id, 's0m31D')
341 self.assertEquals(result.name, 'chrooome')
342 self.assertEquals(result.os, 'Linux')
343
344
345class UtilsTest(cros_test_lib.TestCase):
Don Garrettf8bf7842014-03-20 17:03:42 -0700346 """Tests ReadSymsHeader."""
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400347
348 def testReadSymsHeaderGoodBuffer(self):
349 """Make sure ReadSymsHeader can parse sym file handles"""
350 result = cros_generate_breakpad_symbols.ReadSymsHeader(
351 StringIO.StringIO('MODULE Linux arm MY-ID-HERE blkid'))
352 self.assertEquals(result.cpu, 'arm')
353 self.assertEquals(result.id, 'MY-ID-HERE')
354 self.assertEquals(result.name, 'blkid')
355 self.assertEquals(result.os, 'Linux')
356
357 def testReadSymsHeaderBadd(self):
358 """Make sure ReadSymsHeader throws on bad sym files"""
359 self.assertRaises(ValueError, cros_generate_breakpad_symbols.ReadSymsHeader,
360 StringIO.StringIO('asdf'))
361
362 def testBreakpadDir(self):
363 """Make sure board->breakpad path expansion works"""
364 expected = '/build/blah/usr/lib/debug/breakpad'
365 result = cros_generate_breakpad_symbols.FindBreakpadDir('blah')
366 self.assertEquals(expected, result)
367
368 def testDebugDir(self):
369 """Make sure board->debug path expansion works"""
370 expected = '/build/blah/usr/lib/debug'
371 result = cros_generate_breakpad_symbols.FindDebugDir('blah')
372 self.assertEquals(expected, result)
373
374
Mike Frysingerea838d12014-12-08 11:55:32 -0500375def main(_argv):
Mike Frysinger27e21b72018-07-12 14:20:21 -0400376 # pylint: disable=protected-access
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400377 # Set timeouts small so that if the unit test hangs, it won't hang for long.
378 parallel._BackgroundTask.STARTUP_TIMEOUT = 5
379 parallel._BackgroundTask.EXIT_TIMEOUT = 5
380
381 # Run the tests.
Mike Frysingerba167372015-01-21 10:37:03 -0500382 cros_test_lib.main(level='info', module=__name__)