Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
1 import subprocess
2 import unittest
3 import sys
4 import os
5 import imp
6 from tempfile import mkdtemp
7 from shutil import rmtree
8 import mozunit
10 from UserString import UserString
11 # Create a controlled configuration for use by expandlibs
12 config_win = {
13 'AR': 'lib',
14 'AR_EXTRACT': '',
15 'DLL_PREFIX': '',
16 'LIB_PREFIX': '',
17 'OBJ_SUFFIX': '.obj',
18 'LIB_SUFFIX': '.lib',
19 'DLL_SUFFIX': '.dll',
20 'IMPORT_LIB_SUFFIX': '.lib',
21 'LIBS_DESC_SUFFIX': '.desc',
22 'EXPAND_LIBS_LIST_STYLE': 'list',
23 }
24 config_unix = {
25 'AR': 'ar',
26 'AR_EXTRACT': 'ar -x',
27 'DLL_PREFIX': 'lib',
28 'LIB_PREFIX': 'lib',
29 'OBJ_SUFFIX': '.o',
30 'LIB_SUFFIX': '.a',
31 'DLL_SUFFIX': '.so',
32 'IMPORT_LIB_SUFFIX': '',
33 'LIBS_DESC_SUFFIX': '.desc',
34 'EXPAND_LIBS_LIST_STYLE': 'linkerscript',
35 }
37 config = sys.modules['expandlibs_config'] = imp.new_module('expandlibs_config')
39 from expandlibs import LibDescriptor, ExpandArgs, relativize, ExpandLibsDeps
40 from expandlibs_gen import generate
41 from expandlibs_exec import ExpandArgsMore, SectionFinder
43 def Lib(name):
44 return config.LIB_PREFIX + name + config.LIB_SUFFIX
46 def Obj(name):
47 return name + config.OBJ_SUFFIX
49 def Dll(name):
50 return config.DLL_PREFIX + name + config.DLL_SUFFIX
52 def ImportLib(name):
53 if not len(config.IMPORT_LIB_SUFFIX): return Dll(name)
54 return config.LIB_PREFIX + name + config.IMPORT_LIB_SUFFIX
56 class TestRelativize(unittest.TestCase):
57 def test_relativize(self):
58 '''Test relativize()'''
59 os_path_exists = os.path.exists
60 def exists(path):
61 return True
62 os.path.exists = exists
63 self.assertEqual(relativize(os.path.abspath(os.curdir)), os.curdir)
64 self.assertEqual(relativize(os.path.abspath(os.pardir)), os.pardir)
65 self.assertEqual(relativize(os.path.join(os.curdir, 'a')), 'a')
66 self.assertEqual(relativize(os.path.join(os.path.abspath(os.curdir), 'a')), 'a')
67 # relativize is expected to return the absolute path if it is shorter
68 self.assertEqual(relativize(os.sep), os.sep)
69 os.path.exists = os.path.exists
71 class TestLibDescriptor(unittest.TestCase):
72 def test_serialize(self):
73 '''Test LibDescriptor's serialization'''
74 desc = LibDescriptor()
75 desc[LibDescriptor.KEYS[0]] = ['a', 'b']
76 self.assertEqual(str(desc), "{0} = a b".format(LibDescriptor.KEYS[0]))
77 desc['unsupported-key'] = ['a']
78 self.assertEqual(str(desc), "{0} = a b".format(LibDescriptor.KEYS[0]))
79 desc[LibDescriptor.KEYS[1]] = ['c', 'd', 'e']
80 self.assertEqual(str(desc),
81 "{0} = a b\n{1} = c d e"
82 .format(LibDescriptor.KEYS[0], LibDescriptor.KEYS[1]))
83 desc[LibDescriptor.KEYS[0]] = []
84 self.assertEqual(str(desc), "{0} = c d e".format(LibDescriptor.KEYS[1]))
86 def test_read(self):
87 '''Test LibDescriptor's initialization'''
88 desc_list = ["# Comment",
89 "{0} = a b".format(LibDescriptor.KEYS[1]),
90 "", # Empty line
91 "foo = bar", # Should be discarded
92 "{0} = c d e".format(LibDescriptor.KEYS[0])]
93 desc = LibDescriptor(desc_list)
94 self.assertEqual(desc[LibDescriptor.KEYS[1]], ['a', 'b'])
95 self.assertEqual(desc[LibDescriptor.KEYS[0]], ['c', 'd', 'e'])
96 self.assertEqual(False, 'foo' in desc)
98 def wrap_method(conf, wrapped_method):
99 '''Wrapper used to call a test with a specific configuration'''
100 def _method(self):
101 for key in conf:
102 setattr(config, key, conf[key])
103 self.init()
104 try:
105 wrapped_method(self)
106 except:
107 raise
108 finally:
109 self.cleanup()
110 return _method
112 class ReplicateTests(type):
113 '''Replicates tests for unix and windows variants'''
114 def __new__(cls, clsName, bases, dict):
115 for name in [key for key in dict if key.startswith('test_')]:
116 dict[name + '_unix'] = wrap_method(config_unix, dict[name])
117 dict[name + '_unix'].__doc__ = dict[name].__doc__ + ' (unix)'
118 dict[name + '_win'] = wrap_method(config_win, dict[name])
119 dict[name + '_win'].__doc__ = dict[name].__doc__ + ' (win)'
120 del dict[name]
121 return type.__new__(cls, clsName, bases, dict)
123 class TestCaseWithTmpDir(unittest.TestCase):
124 __metaclass__ = ReplicateTests
125 def init(self):
126 self.tmpdir = os.path.abspath(mkdtemp(dir=os.curdir))
128 def cleanup(self):
129 rmtree(self.tmpdir)
131 def touch(self, files):
132 for f in files:
133 open(f, 'w').close()
135 def tmpfile(self, *args):
136 return os.path.join(self.tmpdir, *args)
138 class TestExpandLibsGen(TestCaseWithTmpDir):
139 def test_generate(self):
140 '''Test library descriptor generation'''
141 files = [self.tmpfile(f) for f in
142 [Lib('a'), Obj('b'), Lib('c'), Obj('d'), Obj('e'), Lib('f')]]
143 self.touch(files[:-1])
144 self.touch([files[-1] + config.LIBS_DESC_SUFFIX])
146 desc = generate(files)
147 self.assertEqual(desc['OBJS'], [self.tmpfile(Obj(s)) for s in ['b', 'd', 'e']])
148 self.assertEqual(desc['LIBS'], [self.tmpfile(Lib(s)) for s in ['a', 'c', 'f']])
150 self.assertRaises(Exception, generate, files + [self.tmpfile(Obj('z'))])
151 self.assertRaises(Exception, generate, files + [self.tmpfile(Lib('y'))])
153 class TestExpandInit(TestCaseWithTmpDir):
154 def init(self):
155 ''' Initializes test environment for library expansion tests'''
156 super(TestExpandInit, self).init()
157 # Create 2 fake libraries, each containing 3 objects, and the second
158 # including the first one and another library.
159 os.mkdir(self.tmpfile('libx'))
160 os.mkdir(self.tmpfile('liby'))
161 self.libx_files = [self.tmpfile('libx', Obj(f)) for f in ['g', 'h', 'i']]
162 self.liby_files = [self.tmpfile('liby', Obj(f)) for f in ['j', 'k', 'l']] + [self.tmpfile('liby', Lib('z'))]
163 self.touch(self.libx_files + self.liby_files)
164 with open(self.tmpfile('libx', Lib('x') + config.LIBS_DESC_SUFFIX), 'w') as f:
165 f.write(str(generate(self.libx_files)))
166 with open(self.tmpfile('liby', Lib('y') + config.LIBS_DESC_SUFFIX), 'w') as f:
167 f.write(str(generate(self.liby_files + [self.tmpfile('libx', Lib('x'))])))
169 # Create various objects and libraries
170 self.arg_files = [self.tmpfile(f) for f in [Lib('a'), Obj('b'), Obj('c'), Lib('d'), Obj('e')]]
171 # We always give library names (LIB_PREFIX/SUFFIX), even for
172 # dynamic/import libraries
173 self.files = self.arg_files + [self.tmpfile(ImportLib('f'))]
174 self.arg_files += [self.tmpfile(Lib('f'))]
175 self.touch(self.files)
177 def assertRelEqual(self, args1, args2):
178 self.assertEqual(args1, [relativize(a) for a in args2])
180 class TestExpandArgs(TestExpandInit):
181 def test_expand(self):
182 '''Test library expansion'''
183 # Expanding arguments means libraries with a descriptor are expanded
184 # with the descriptor content, and import libraries are used when
185 # a library doesn't exist
186 args = ExpandArgs(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))])
187 self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + self.libx_files)
189 # When a library exists at the same time as a descriptor, we just use
190 # the library
191 self.touch([self.tmpfile('libx', Lib('x'))])
192 args = ExpandArgs(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))])
193 self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + [self.tmpfile('libx', Lib('x'))])
195 self.touch([self.tmpfile('liby', Lib('y'))])
196 args = ExpandArgs(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))])
197 self.assertRelEqual(args, ['foo', '-bar'] + self.files + [self.tmpfile('liby', Lib('y'))])
199 class TestExpandLibsDeps(TestExpandInit):
200 def test_expandlibsdeps(self):
201 '''Test library expansion for dependencies'''
202 # Dependency list for a library with a descriptor is equivalent to
203 # the arguments expansion, to which we add each descriptor
204 args = self.arg_files + [self.tmpfile('liby', Lib('y'))]
205 self.assertRelEqual(ExpandLibsDeps(args), ExpandArgs(args) + [self.tmpfile('libx', Lib('x') + config.LIBS_DESC_SUFFIX), self.tmpfile('liby', Lib('y') + config.LIBS_DESC_SUFFIX)])
207 # When a library exists at the same time as a descriptor, the
208 # descriptor is not a dependency
209 self.touch([self.tmpfile('libx', Lib('x'))])
210 args = self.arg_files + [self.tmpfile('liby', Lib('y'))]
211 self.assertRelEqual(ExpandLibsDeps(args), ExpandArgs(args) + [self.tmpfile('liby', Lib('y') + config.LIBS_DESC_SUFFIX)])
213 self.touch([self.tmpfile('liby', Lib('y'))])
214 args = self.arg_files + [self.tmpfile('liby', Lib('y'))]
215 self.assertRelEqual(ExpandLibsDeps(args), ExpandArgs(args))
217 class TestExpandArgsMore(TestExpandInit):
218 def test_makelist(self):
219 '''Test grouping object files in lists'''
220 # ExpandArgsMore does the same as ExpandArgs
221 with ExpandArgsMore(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) as args:
222 self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + self.libx_files)
224 # But also has an extra method replacing object files with a list
225 args.makelist()
226 # self.files has objects at #1, #2, #4
227 self.assertRelEqual(args[:3], ['foo', '-bar'] + self.files[:1])
228 self.assertRelEqual(args[4:], [self.files[3]] + self.files[5:] + [self.tmpfile('liby', Lib('z'))])
230 # Check the list file content
231 objs = [f for f in self.files + self.liby_files + self.libx_files if f.endswith(config.OBJ_SUFFIX)]
232 if config.EXPAND_LIBS_LIST_STYLE == "linkerscript":
233 self.assertNotEqual(args[3][0], '@')
234 filename = args[3]
235 content = ['INPUT("{0}")'.format(relativize(f)) for f in objs]
236 with open(filename, 'r') as f:
237 self.assertEqual([l.strip() for l in f.readlines() if len(l.strip())], content)
238 elif config.EXPAND_LIBS_LIST_STYLE == "list":
239 self.assertEqual(args[3][0], '@')
240 filename = args[3][1:]
241 content = objs
242 with open(filename, 'r') as f:
243 self.assertRelEqual([l.strip() for l in f.readlines() if len(l.strip())], content)
245 tmp = args.tmp
246 # Check that all temporary files are properly removed
247 self.assertEqual(True, all([not os.path.exists(f) for f in tmp]))
249 def test_extract(self):
250 '''Test library extraction'''
251 # Divert subprocess.call
252 subprocess_call = subprocess.call
253 extracted = {}
254 def call(args, **kargs):
255 if config.AR == 'lib':
256 self.assertEqual(args[:2], [config.AR, '-NOLOGO'])
257 self.assertTrue(args[2].startswith('-EXTRACT:'))
258 extract = [args[2][len('-EXTRACT:'):]]
259 self.assertTrue(extract)
260 args = args[3:]
261 else:
262 # The command called is always AR_EXTRACT
263 ar_extract = config.AR_EXTRACT.split()
264 self.assertEqual(args[:len(ar_extract)], ar_extract)
265 args = args[len(ar_extract):]
266 # Remaining argument is always one library
267 self.assertEqual(len(args), 1)
268 arg = args[0]
269 self.assertEqual(os.path.splitext(arg)[1], config.LIB_SUFFIX)
270 # Simulate file extraction
271 lib = os.path.splitext(os.path.basename(arg))[0]
272 if config.AR != 'lib':
273 extract = [lib, lib + '2']
274 extract = [os.path.join(kargs['cwd'], f) for f in extract]
275 if config.AR != 'lib':
276 extract = [Obj(f) for f in extract]
277 if not lib in extracted:
278 extracted[lib] = []
279 extracted[lib].extend(extract)
280 self.touch(extract)
281 subprocess.call = call
283 def check_output(args, **kargs):
284 # The command called is always AR
285 ar = config.AR
286 self.assertEqual(args[0:3], [ar, '-NOLOGO', '-LIST'])
287 # Remaining argument is always one library
288 self.assertRelEqual([os.path.splitext(arg)[1] for arg in args[3:]],
289 [config.LIB_SUFFIX])
290 # Simulate LIB -NOLOGO -LIST
291 lib = os.path.splitext(os.path.basename(args[3]))[0]
292 return '%s\n%s\n' % (Obj(lib), Obj(lib + '2'))
293 subprocess.check_output = check_output
295 # ExpandArgsMore does the same as ExpandArgs
296 self.touch([self.tmpfile('liby', Lib('y'))])
297 with ExpandArgsMore(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) as args:
298 self.assertRelEqual(args, ['foo', '-bar'] + self.files + [self.tmpfile('liby', Lib('y'))])
300 # ExpandArgsMore also has an extra method extracting static libraries
301 # when possible
302 args.extract()
304 files = self.files + self.liby_files + self.libx_files
305 # With AR_EXTRACT, it uses the descriptors when there are, and
306 # actually
307 # extracts the remaining libraries
308 extracted_args = []
309 for f in files:
310 if f.endswith(config.LIB_SUFFIX):
311 extracted_args.extend(sorted(extracted[os.path.splitext(os.path.basename(f))[0]]))
312 else:
313 extracted_args.append(f)
314 self.assertRelEqual(args, ['foo', '-bar'] + extracted_args)
316 tmp = args.tmp
317 # Check that all temporary files are properly removed
318 self.assertEqual(True, all([not os.path.exists(f) for f in tmp]))
320 # Restore subprocess.call
321 subprocess.call = subprocess_call
323 class FakeProcess(object):
324 def __init__(self, out, err = ''):
325 self.out = out
326 self.err = err
328 def communicate(self):
329 return (self.out, self.err)
331 OBJDUMPS = {
332 'foo.o': '''
333 00000000 g F .text\t00000001 foo
334 00000000 g F .text._Z6foobarv\t00000001 _Z6foobarv
335 00000000 g F .text.hello\t00000001 hello
336 00000000 g F .text._ZThn4_6foobarv\t00000001 _ZThn4_6foobarv
337 ''',
338 'bar.o': '''
339 00000000 g F .text.hi\t00000001 hi
340 00000000 g F .text.hot._Z6barbazv\t00000001 .hidden _Z6barbazv
341 ''',
342 }
344 PRINT_ICF = '''
345 ld: ICF folding section '.text.hello' in file 'foo.o'into '.text.hi' in file 'bar.o'
346 ld: ICF folding section '.foo' in file 'foo.o'into '.foo' in file 'bar.o'
347 '''
349 class SubprocessPopen(object):
350 def __init__(self, test):
351 self.test = test
353 def __call__(self, args, stdout = None, stderr = None):
354 self.test.assertEqual(stdout, subprocess.PIPE)
355 self.test.assertEqual(stderr, subprocess.PIPE)
356 if args[0] == 'objdump':
357 self.test.assertEqual(args[1], '-t')
358 self.test.assertTrue(args[2] in OBJDUMPS)
359 return FakeProcess(OBJDUMPS[args[2]])
360 else:
361 return FakeProcess('', PRINT_ICF)
363 class TestSectionFinder(unittest.TestCase):
364 def test_getSections(self):
365 '''Test SectionFinder'''
366 # Divert subprocess.Popen
367 subprocess_popen = subprocess.Popen
368 subprocess.Popen = SubprocessPopen(self)
369 config.EXPAND_LIBS_ORDER_STYLE = 'linkerscript'
370 config.OBJ_SUFFIX = '.o'
371 config.LIB_SUFFIX = '.a'
372 finder = SectionFinder(['foo.o', 'bar.o'])
373 self.assertEqual(finder.getSections('foobar'), [])
374 self.assertEqual(finder.getSections('_Z6barbazv'), ['.text.hot._Z6barbazv'])
375 self.assertEqual(finder.getSections('_Z6foobarv'), ['.text._Z6foobarv', '.text._ZThn4_6foobarv'])
376 self.assertEqual(finder.getSections('_ZThn4_6foobarv'), ['.text._Z6foobarv', '.text._ZThn4_6foobarv'])
377 subprocess.Popen = subprocess_popen
379 class TestSymbolOrder(unittest.TestCase):
380 def test_getOrderedSections(self):
381 '''Test ExpandMoreArgs' _getOrderedSections'''
382 # Divert subprocess.Popen
383 subprocess_popen = subprocess.Popen
384 subprocess.Popen = SubprocessPopen(self)
385 config.EXPAND_LIBS_ORDER_STYLE = 'linkerscript'
386 config.OBJ_SUFFIX = '.o'
387 config.LIB_SUFFIX = '.a'
388 config.LD_PRINT_ICF_SECTIONS = ''
389 args = ExpandArgsMore(['foo', '-bar', 'bar.o', 'foo.o'])
390 self.assertEqual(args._getOrderedSections(['_Z6foobarv', '_Z6barbazv']), ['.text._Z6foobarv', '.text._ZThn4_6foobarv', '.text.hot._Z6barbazv'])
391 self.assertEqual(args._getOrderedSections(['_ZThn4_6foobarv', '_Z6barbazv']), ['.text._Z6foobarv', '.text._ZThn4_6foobarv', '.text.hot._Z6barbazv'])
392 subprocess.Popen = subprocess_popen
394 def test_getFoldedSections(self):
395 '''Test ExpandMoreArgs' _getFoldedSections'''
396 # Divert subprocess.Popen
397 subprocess_popen = subprocess.Popen
398 subprocess.Popen = SubprocessPopen(self)
399 config.LD_PRINT_ICF_SECTIONS = '-Wl,--print-icf-sections'
400 args = ExpandArgsMore(['foo', '-bar', 'bar.o', 'foo.o'])
401 self.assertEqual(args._getFoldedSections(), {'.text.hello': ['.text.hi'], '.text.hi': ['.text.hello']})
402 subprocess.Popen = subprocess_popen
404 def test_getOrderedSectionsWithICF(self):
405 '''Test ExpandMoreArgs' _getOrderedSections, with ICF'''
406 # Divert subprocess.Popen
407 subprocess_popen = subprocess.Popen
408 subprocess.Popen = SubprocessPopen(self)
409 config.EXPAND_LIBS_ORDER_STYLE = 'linkerscript'
410 config.OBJ_SUFFIX = '.o'
411 config.LIB_SUFFIX = '.a'
412 config.LD_PRINT_ICF_SECTIONS = '-Wl,--print-icf-sections'
413 args = ExpandArgsMore(['foo', '-bar', 'bar.o', 'foo.o'])
414 self.assertEqual(args._getOrderedSections(['hello', '_Z6barbazv']), ['.text.hello', '.text.hi', '.text.hot._Z6barbazv'])
415 self.assertEqual(args._getOrderedSections(['_ZThn4_6foobarv', 'hi', '_Z6barbazv']), ['.text._Z6foobarv', '.text._ZThn4_6foobarv', '.text.hi', '.text.hello', '.text.hot._Z6barbazv'])
416 subprocess.Popen = subprocess_popen
419 if __name__ == '__main__':
420 mozunit.main()