Bumping manifests a=b2g-bump
[gecko.git] / config / tests / unit-expandlibs.py
blob4a433c1c992f19ffd20ecc5d759797861bb4b92a
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',
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',
37 config = sys.modules['expandlibs_config'] = imp.new_module('expandlibs_config')
39 from expandlibs import LibDescriptor, ExpandArgs, relativize
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 TestExpandArgsMore(TestExpandInit):
200 def test_makelist(self):
201 '''Test grouping object files in lists'''
202 # ExpandArgsMore does the same as ExpandArgs
203 with ExpandArgsMore(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) as args:
204 self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + self.libx_files)
206 # But also has an extra method replacing object files with a list
207 args.makelist()
208 # self.files has objects at #1, #2, #4
209 self.assertRelEqual(args[:3], ['foo', '-bar'] + self.files[:1])
210 self.assertRelEqual(args[4:], [self.files[3]] + self.files[5:] + [self.tmpfile('liby', Lib('z'))])
212 # Check the list file content
213 objs = [f for f in self.files + self.liby_files + self.libx_files if f.endswith(config.OBJ_SUFFIX)]
214 if config.EXPAND_LIBS_LIST_STYLE == "linkerscript":
215 self.assertNotEqual(args[3][0], '@')
216 filename = args[3]
217 content = ['INPUT("{0}")'.format(relativize(f)) for f in objs]
218 with open(filename, 'r') as f:
219 self.assertEqual([l.strip() for l in f.readlines() if len(l.strip())], content)
220 elif config.EXPAND_LIBS_LIST_STYLE == "list":
221 self.assertEqual(args[3][0], '@')
222 filename = args[3][1:]
223 content = objs
224 with open(filename, 'r') as f:
225 self.assertRelEqual([l.strip() for l in f.readlines() if len(l.strip())], content)
227 tmp = args.tmp
228 # Check that all temporary files are properly removed
229 self.assertEqual(True, all([not os.path.exists(f) for f in tmp]))
231 def test_extract(self):
232 '''Test library extraction'''
233 # Divert subprocess.call
234 subprocess_call = subprocess.call
235 subprocess_check_output = subprocess.check_output
236 def call(args, **kargs):
237 if config.AR == 'lib':
238 self.assertEqual(args[:2], [config.AR, '-NOLOGO'])
239 self.assertTrue(args[2].startswith('-EXTRACT:'))
240 extract = [args[2][len('-EXTRACT:'):]]
241 self.assertTrue(extract)
242 args = args[3:]
243 else:
244 # The command called is always AR_EXTRACT
245 ar_extract = config.AR_EXTRACT.split()
246 self.assertEqual(args[:len(ar_extract)], ar_extract)
247 args = args[len(ar_extract):]
248 # Remaining argument is always one library
249 self.assertEqual(len(args), 1)
250 arg = args[0]
251 self.assertEqual(os.path.splitext(arg)[1], config.LIB_SUFFIX)
252 # Simulate file extraction
253 lib = os.path.splitext(os.path.basename(arg))[0]
254 if config.AR != 'lib':
255 extract = [lib, lib + '2']
256 extract = [os.path.join(kargs['cwd'], f) for f in extract]
257 if config.AR != 'lib':
258 extract = [Obj(f) for f in extract]
259 if not lib in extracted:
260 extracted[lib] = []
261 extracted[lib].extend(extract)
262 self.touch(extract)
263 subprocess.call = call
265 def check_output(args, **kargs):
266 # The command called is always AR
267 ar = config.AR
268 self.assertEqual(args[0:3], [ar, '-NOLOGO', '-LIST'])
269 # Remaining argument is always one library
270 self.assertRelEqual([os.path.splitext(arg)[1] for arg in args[3:]],
271 [config.LIB_SUFFIX])
272 # Simulate LIB -NOLOGO -LIST
273 lib = os.path.splitext(os.path.basename(args[3]))[0]
274 return '%s\n%s\n' % (Obj(lib), Obj(lib + '2'))
275 subprocess.check_output = check_output
277 # ExpandArgsMore does the same as ExpandArgs
278 self.touch([self.tmpfile('liby', Lib('y'))])
279 for iteration in (1, 2):
280 with ExpandArgsMore(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) as args:
281 self.assertRelEqual(args, ['foo', '-bar'] + self.files + [self.tmpfile('liby', Lib('y'))])
283 extracted = {}
284 # ExpandArgsMore also has an extra method extracting static libraries
285 # when possible
286 args.extract()
288 files = self.files + self.liby_files + self.libx_files
289 # With AR_EXTRACT, it uses the descriptors when there are, and
290 # actually
291 # extracts the remaining libraries
292 extracted_args = []
293 for f in files:
294 if f.endswith(config.LIB_SUFFIX):
295 base = os.path.splitext(os.path.basename(f))[0]
296 # On the first iteration, we test the behavior of
297 # extracting archives that don't have a copy of their
298 # contents next to them, which is to use the file
299 # extracted from the archive in a temporary directory.
300 # On the second iteration, we test extracting archives
301 # that do have a copy of their contents next to them,
302 # in which case those contents are used instead of the
303 # temporarily extracted files.
304 if iteration == 1:
305 extracted_args.extend(sorted(extracted[base]))
306 else:
307 dirname = os.path.dirname(f[len(self.tmpdir)+1:])
308 if base.endswith('f'):
309 dirname = os.path.join(dirname, 'foo', 'bar')
310 extracted_args.extend([self.tmpfile(dirname, Obj(base)), self.tmpfile(dirname, Obj(base + '2'))])
311 else:
312 extracted_args.append(f)
313 self.assertRelEqual(args, ['foo', '-bar'] + extracted_args)
315 tmp = args.tmp
316 # Check that all temporary files are properly removed
317 self.assertEqual(True, all([not os.path.exists(f) for f in tmp]))
319 # Create archives contents next to them for the second iteration.
320 base = os.path.splitext(Lib('_'))[0]
321 self.touch(self.tmpfile(Obj(base.replace('_', suffix))) for suffix in ('a', 'a2', 'd', 'd2'))
322 try:
323 os.makedirs(self.tmpfile('foo', 'bar'))
324 except:
325 pass
326 self.touch(self.tmpfile('foo', 'bar', Obj(base.replace('_', suffix))) for suffix in ('f', 'f2'))
327 self.touch(self.tmpfile('liby', Obj(base.replace('_', suffix))) for suffix in ('z', 'z2'))
329 # Restore subprocess.call and subprocess.check_output
330 subprocess.call = subprocess_call
331 subprocess.check_output = subprocess_check_output
333 class FakeProcess(object):
334 def __init__(self, out, err = ''):
335 self.out = out
336 self.err = err
338 def communicate(self):
339 return (self.out, self.err)
341 OBJDUMPS = {
342 'foo.o': '''
343 00000000 g F .text\t00000001 foo
344 00000000 g F .text._Z6foobarv\t00000001 _Z6foobarv
345 00000000 g F .text.hello\t00000001 hello
346 00000000 g F .text._ZThn4_6foobarv\t00000001 _ZThn4_6foobarv
347 ''',
348 'bar.o': '''
349 00000000 g F .text.hi\t00000001 hi
350 00000000 g F .text.hot._Z6barbazv\t00000001 .hidden _Z6barbazv
351 ''',
354 PRINT_ICF = '''
355 ld: ICF folding section '.text.hello' in file 'foo.o'into '.text.hi' in file 'bar.o'
356 ld: ICF folding section '.foo' in file 'foo.o'into '.foo' in file 'bar.o'
359 class SubprocessPopen(object):
360 def __init__(self, test):
361 self.test = test
363 def __call__(self, args, stdout = None, stderr = None):
364 self.test.assertEqual(stdout, subprocess.PIPE)
365 self.test.assertEqual(stderr, subprocess.PIPE)
366 if args[0] == 'objdump':
367 self.test.assertEqual(args[1], '-t')
368 self.test.assertTrue(args[2] in OBJDUMPS)
369 return FakeProcess(OBJDUMPS[args[2]])
370 else:
371 return FakeProcess('', PRINT_ICF)
373 class TestSectionFinder(unittest.TestCase):
374 def test_getSections(self):
375 '''Test SectionFinder'''
376 # Divert subprocess.Popen
377 subprocess_popen = subprocess.Popen
378 subprocess.Popen = SubprocessPopen(self)
379 config.EXPAND_LIBS_ORDER_STYLE = 'linkerscript'
380 config.OBJ_SUFFIX = '.o'
381 config.LIB_SUFFIX = '.a'
382 finder = SectionFinder(['foo.o', 'bar.o'])
383 self.assertEqual(finder.getSections('foobar'), [])
384 self.assertEqual(finder.getSections('_Z6barbazv'), ['.text.hot._Z6barbazv'])
385 self.assertEqual(finder.getSections('_Z6foobarv'), ['.text._Z6foobarv', '.text._ZThn4_6foobarv'])
386 self.assertEqual(finder.getSections('_ZThn4_6foobarv'), ['.text._Z6foobarv', '.text._ZThn4_6foobarv'])
387 subprocess.Popen = subprocess_popen
389 class TestSymbolOrder(unittest.TestCase):
390 def test_getOrderedSections(self):
391 '''Test ExpandMoreArgs' _getOrderedSections'''
392 # Divert subprocess.Popen
393 subprocess_popen = subprocess.Popen
394 subprocess.Popen = SubprocessPopen(self)
395 config.EXPAND_LIBS_ORDER_STYLE = 'linkerscript'
396 config.OBJ_SUFFIX = '.o'
397 config.LIB_SUFFIX = '.a'
398 config.LD_PRINT_ICF_SECTIONS = ''
399 args = ExpandArgsMore(['foo', '-bar', 'bar.o', 'foo.o'])
400 self.assertEqual(args._getOrderedSections(['_Z6foobarv', '_Z6barbazv']), ['.text._Z6foobarv', '.text._ZThn4_6foobarv', '.text.hot._Z6barbazv'])
401 self.assertEqual(args._getOrderedSections(['_ZThn4_6foobarv', '_Z6barbazv']), ['.text._Z6foobarv', '.text._ZThn4_6foobarv', '.text.hot._Z6barbazv'])
402 subprocess.Popen = subprocess_popen
404 def test_getFoldedSections(self):
405 '''Test ExpandMoreArgs' _getFoldedSections'''
406 # Divert subprocess.Popen
407 subprocess_popen = subprocess.Popen
408 subprocess.Popen = SubprocessPopen(self)
409 config.LD_PRINT_ICF_SECTIONS = '-Wl,--print-icf-sections'
410 args = ExpandArgsMore(['foo', '-bar', 'bar.o', 'foo.o'])
411 self.assertEqual(args._getFoldedSections(), {'.text.hello': ['.text.hi'], '.text.hi': ['.text.hello']})
412 subprocess.Popen = subprocess_popen
414 def test_getOrderedSectionsWithICF(self):
415 '''Test ExpandMoreArgs' _getOrderedSections, with ICF'''
416 # Divert subprocess.Popen
417 subprocess_popen = subprocess.Popen
418 subprocess.Popen = SubprocessPopen(self)
419 config.EXPAND_LIBS_ORDER_STYLE = 'linkerscript'
420 config.OBJ_SUFFIX = '.o'
421 config.LIB_SUFFIX = '.a'
422 config.LD_PRINT_ICF_SECTIONS = '-Wl,--print-icf-sections'
423 args = ExpandArgsMore(['foo', '-bar', 'bar.o', 'foo.o'])
424 self.assertEqual(args._getOrderedSections(['hello', '_Z6barbazv']), ['.text.hello', '.text.hi', '.text.hot._Z6barbazv'])
425 self.assertEqual(args._getOrderedSections(['_ZThn4_6foobarv', 'hi', '_Z6barbazv']), ['.text._Z6foobarv', '.text._ZThn4_6foobarv', '.text.hi', '.text.hello', '.text.hot._Z6barbazv'])
426 subprocess.Popen = subprocess_popen
429 if __name__ == '__main__':
430 mozunit.main()