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