1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/config/tests/unit-expandlibs.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,420 @@ 1.4 +import subprocess 1.5 +import unittest 1.6 +import sys 1.7 +import os 1.8 +import imp 1.9 +from tempfile import mkdtemp 1.10 +from shutil import rmtree 1.11 +import mozunit 1.12 + 1.13 +from UserString import UserString 1.14 +# Create a controlled configuration for use by expandlibs 1.15 +config_win = { 1.16 + 'AR': 'lib', 1.17 + 'AR_EXTRACT': '', 1.18 + 'DLL_PREFIX': '', 1.19 + 'LIB_PREFIX': '', 1.20 + 'OBJ_SUFFIX': '.obj', 1.21 + 'LIB_SUFFIX': '.lib', 1.22 + 'DLL_SUFFIX': '.dll', 1.23 + 'IMPORT_LIB_SUFFIX': '.lib', 1.24 + 'LIBS_DESC_SUFFIX': '.desc', 1.25 + 'EXPAND_LIBS_LIST_STYLE': 'list', 1.26 +} 1.27 +config_unix = { 1.28 + 'AR': 'ar', 1.29 + 'AR_EXTRACT': 'ar -x', 1.30 + 'DLL_PREFIX': 'lib', 1.31 + 'LIB_PREFIX': 'lib', 1.32 + 'OBJ_SUFFIX': '.o', 1.33 + 'LIB_SUFFIX': '.a', 1.34 + 'DLL_SUFFIX': '.so', 1.35 + 'IMPORT_LIB_SUFFIX': '', 1.36 + 'LIBS_DESC_SUFFIX': '.desc', 1.37 + 'EXPAND_LIBS_LIST_STYLE': 'linkerscript', 1.38 +} 1.39 + 1.40 +config = sys.modules['expandlibs_config'] = imp.new_module('expandlibs_config') 1.41 + 1.42 +from expandlibs import LibDescriptor, ExpandArgs, relativize, ExpandLibsDeps 1.43 +from expandlibs_gen import generate 1.44 +from expandlibs_exec import ExpandArgsMore, SectionFinder 1.45 + 1.46 +def Lib(name): 1.47 + return config.LIB_PREFIX + name + config.LIB_SUFFIX 1.48 + 1.49 +def Obj(name): 1.50 + return name + config.OBJ_SUFFIX 1.51 + 1.52 +def Dll(name): 1.53 + return config.DLL_PREFIX + name + config.DLL_SUFFIX 1.54 + 1.55 +def ImportLib(name): 1.56 + if not len(config.IMPORT_LIB_SUFFIX): return Dll(name) 1.57 + return config.LIB_PREFIX + name + config.IMPORT_LIB_SUFFIX 1.58 + 1.59 +class TestRelativize(unittest.TestCase): 1.60 + def test_relativize(self): 1.61 + '''Test relativize()''' 1.62 + os_path_exists = os.path.exists 1.63 + def exists(path): 1.64 + return True 1.65 + os.path.exists = exists 1.66 + self.assertEqual(relativize(os.path.abspath(os.curdir)), os.curdir) 1.67 + self.assertEqual(relativize(os.path.abspath(os.pardir)), os.pardir) 1.68 + self.assertEqual(relativize(os.path.join(os.curdir, 'a')), 'a') 1.69 + self.assertEqual(relativize(os.path.join(os.path.abspath(os.curdir), 'a')), 'a') 1.70 + # relativize is expected to return the absolute path if it is shorter 1.71 + self.assertEqual(relativize(os.sep), os.sep) 1.72 + os.path.exists = os.path.exists 1.73 + 1.74 +class TestLibDescriptor(unittest.TestCase): 1.75 + def test_serialize(self): 1.76 + '''Test LibDescriptor's serialization''' 1.77 + desc = LibDescriptor() 1.78 + desc[LibDescriptor.KEYS[0]] = ['a', 'b'] 1.79 + self.assertEqual(str(desc), "{0} = a b".format(LibDescriptor.KEYS[0])) 1.80 + desc['unsupported-key'] = ['a'] 1.81 + self.assertEqual(str(desc), "{0} = a b".format(LibDescriptor.KEYS[0])) 1.82 + desc[LibDescriptor.KEYS[1]] = ['c', 'd', 'e'] 1.83 + self.assertEqual(str(desc), 1.84 + "{0} = a b\n{1} = c d e" 1.85 + .format(LibDescriptor.KEYS[0], LibDescriptor.KEYS[1])) 1.86 + desc[LibDescriptor.KEYS[0]] = [] 1.87 + self.assertEqual(str(desc), "{0} = c d e".format(LibDescriptor.KEYS[1])) 1.88 + 1.89 + def test_read(self): 1.90 + '''Test LibDescriptor's initialization''' 1.91 + desc_list = ["# Comment", 1.92 + "{0} = a b".format(LibDescriptor.KEYS[1]), 1.93 + "", # Empty line 1.94 + "foo = bar", # Should be discarded 1.95 + "{0} = c d e".format(LibDescriptor.KEYS[0])] 1.96 + desc = LibDescriptor(desc_list) 1.97 + self.assertEqual(desc[LibDescriptor.KEYS[1]], ['a', 'b']) 1.98 + self.assertEqual(desc[LibDescriptor.KEYS[0]], ['c', 'd', 'e']) 1.99 + self.assertEqual(False, 'foo' in desc) 1.100 + 1.101 +def wrap_method(conf, wrapped_method): 1.102 + '''Wrapper used to call a test with a specific configuration''' 1.103 + def _method(self): 1.104 + for key in conf: 1.105 + setattr(config, key, conf[key]) 1.106 + self.init() 1.107 + try: 1.108 + wrapped_method(self) 1.109 + except: 1.110 + raise 1.111 + finally: 1.112 + self.cleanup() 1.113 + return _method 1.114 + 1.115 +class ReplicateTests(type): 1.116 + '''Replicates tests for unix and windows variants''' 1.117 + def __new__(cls, clsName, bases, dict): 1.118 + for name in [key for key in dict if key.startswith('test_')]: 1.119 + dict[name + '_unix'] = wrap_method(config_unix, dict[name]) 1.120 + dict[name + '_unix'].__doc__ = dict[name].__doc__ + ' (unix)' 1.121 + dict[name + '_win'] = wrap_method(config_win, dict[name]) 1.122 + dict[name + '_win'].__doc__ = dict[name].__doc__ + ' (win)' 1.123 + del dict[name] 1.124 + return type.__new__(cls, clsName, bases, dict) 1.125 + 1.126 +class TestCaseWithTmpDir(unittest.TestCase): 1.127 + __metaclass__ = ReplicateTests 1.128 + def init(self): 1.129 + self.tmpdir = os.path.abspath(mkdtemp(dir=os.curdir)) 1.130 + 1.131 + def cleanup(self): 1.132 + rmtree(self.tmpdir) 1.133 + 1.134 + def touch(self, files): 1.135 + for f in files: 1.136 + open(f, 'w').close() 1.137 + 1.138 + def tmpfile(self, *args): 1.139 + return os.path.join(self.tmpdir, *args) 1.140 + 1.141 +class TestExpandLibsGen(TestCaseWithTmpDir): 1.142 + def test_generate(self): 1.143 + '''Test library descriptor generation''' 1.144 + files = [self.tmpfile(f) for f in 1.145 + [Lib('a'), Obj('b'), Lib('c'), Obj('d'), Obj('e'), Lib('f')]] 1.146 + self.touch(files[:-1]) 1.147 + self.touch([files[-1] + config.LIBS_DESC_SUFFIX]) 1.148 + 1.149 + desc = generate(files) 1.150 + self.assertEqual(desc['OBJS'], [self.tmpfile(Obj(s)) for s in ['b', 'd', 'e']]) 1.151 + self.assertEqual(desc['LIBS'], [self.tmpfile(Lib(s)) for s in ['a', 'c', 'f']]) 1.152 + 1.153 + self.assertRaises(Exception, generate, files + [self.tmpfile(Obj('z'))]) 1.154 + self.assertRaises(Exception, generate, files + [self.tmpfile(Lib('y'))]) 1.155 + 1.156 +class TestExpandInit(TestCaseWithTmpDir): 1.157 + def init(self): 1.158 + ''' Initializes test environment for library expansion tests''' 1.159 + super(TestExpandInit, self).init() 1.160 + # Create 2 fake libraries, each containing 3 objects, and the second 1.161 + # including the first one and another library. 1.162 + os.mkdir(self.tmpfile('libx')) 1.163 + os.mkdir(self.tmpfile('liby')) 1.164 + self.libx_files = [self.tmpfile('libx', Obj(f)) for f in ['g', 'h', 'i']] 1.165 + self.liby_files = [self.tmpfile('liby', Obj(f)) for f in ['j', 'k', 'l']] + [self.tmpfile('liby', Lib('z'))] 1.166 + self.touch(self.libx_files + self.liby_files) 1.167 + with open(self.tmpfile('libx', Lib('x') + config.LIBS_DESC_SUFFIX), 'w') as f: 1.168 + f.write(str(generate(self.libx_files))) 1.169 + with open(self.tmpfile('liby', Lib('y') + config.LIBS_DESC_SUFFIX), 'w') as f: 1.170 + f.write(str(generate(self.liby_files + [self.tmpfile('libx', Lib('x'))]))) 1.171 + 1.172 + # Create various objects and libraries 1.173 + self.arg_files = [self.tmpfile(f) for f in [Lib('a'), Obj('b'), Obj('c'), Lib('d'), Obj('e')]] 1.174 + # We always give library names (LIB_PREFIX/SUFFIX), even for 1.175 + # dynamic/import libraries 1.176 + self.files = self.arg_files + [self.tmpfile(ImportLib('f'))] 1.177 + self.arg_files += [self.tmpfile(Lib('f'))] 1.178 + self.touch(self.files) 1.179 + 1.180 + def assertRelEqual(self, args1, args2): 1.181 + self.assertEqual(args1, [relativize(a) for a in args2]) 1.182 + 1.183 +class TestExpandArgs(TestExpandInit): 1.184 + def test_expand(self): 1.185 + '''Test library expansion''' 1.186 + # Expanding arguments means libraries with a descriptor are expanded 1.187 + # with the descriptor content, and import libraries are used when 1.188 + # a library doesn't exist 1.189 + args = ExpandArgs(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) 1.190 + self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + self.libx_files) 1.191 + 1.192 + # When a library exists at the same time as a descriptor, we just use 1.193 + # the library 1.194 + self.touch([self.tmpfile('libx', Lib('x'))]) 1.195 + args = ExpandArgs(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) 1.196 + self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + [self.tmpfile('libx', Lib('x'))]) 1.197 + 1.198 + self.touch([self.tmpfile('liby', Lib('y'))]) 1.199 + args = ExpandArgs(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) 1.200 + self.assertRelEqual(args, ['foo', '-bar'] + self.files + [self.tmpfile('liby', Lib('y'))]) 1.201 + 1.202 +class TestExpandLibsDeps(TestExpandInit): 1.203 + def test_expandlibsdeps(self): 1.204 + '''Test library expansion for dependencies''' 1.205 + # Dependency list for a library with a descriptor is equivalent to 1.206 + # the arguments expansion, to which we add each descriptor 1.207 + args = self.arg_files + [self.tmpfile('liby', Lib('y'))] 1.208 + self.assertRelEqual(ExpandLibsDeps(args), ExpandArgs(args) + [self.tmpfile('libx', Lib('x') + config.LIBS_DESC_SUFFIX), self.tmpfile('liby', Lib('y') + config.LIBS_DESC_SUFFIX)]) 1.209 + 1.210 + # When a library exists at the same time as a descriptor, the 1.211 + # descriptor is not a dependency 1.212 + self.touch([self.tmpfile('libx', Lib('x'))]) 1.213 + args = self.arg_files + [self.tmpfile('liby', Lib('y'))] 1.214 + self.assertRelEqual(ExpandLibsDeps(args), ExpandArgs(args) + [self.tmpfile('liby', Lib('y') + config.LIBS_DESC_SUFFIX)]) 1.215 + 1.216 + self.touch([self.tmpfile('liby', Lib('y'))]) 1.217 + args = self.arg_files + [self.tmpfile('liby', Lib('y'))] 1.218 + self.assertRelEqual(ExpandLibsDeps(args), ExpandArgs(args)) 1.219 + 1.220 +class TestExpandArgsMore(TestExpandInit): 1.221 + def test_makelist(self): 1.222 + '''Test grouping object files in lists''' 1.223 + # ExpandArgsMore does the same as ExpandArgs 1.224 + with ExpandArgsMore(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) as args: 1.225 + self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + self.libx_files) 1.226 + 1.227 + # But also has an extra method replacing object files with a list 1.228 + args.makelist() 1.229 + # self.files has objects at #1, #2, #4 1.230 + self.assertRelEqual(args[:3], ['foo', '-bar'] + self.files[:1]) 1.231 + self.assertRelEqual(args[4:], [self.files[3]] + self.files[5:] + [self.tmpfile('liby', Lib('z'))]) 1.232 + 1.233 + # Check the list file content 1.234 + objs = [f for f in self.files + self.liby_files + self.libx_files if f.endswith(config.OBJ_SUFFIX)] 1.235 + if config.EXPAND_LIBS_LIST_STYLE == "linkerscript": 1.236 + self.assertNotEqual(args[3][0], '@') 1.237 + filename = args[3] 1.238 + content = ['INPUT("{0}")'.format(relativize(f)) for f in objs] 1.239 + with open(filename, 'r') as f: 1.240 + self.assertEqual([l.strip() for l in f.readlines() if len(l.strip())], content) 1.241 + elif config.EXPAND_LIBS_LIST_STYLE == "list": 1.242 + self.assertEqual(args[3][0], '@') 1.243 + filename = args[3][1:] 1.244 + content = objs 1.245 + with open(filename, 'r') as f: 1.246 + self.assertRelEqual([l.strip() for l in f.readlines() if len(l.strip())], content) 1.247 + 1.248 + tmp = args.tmp 1.249 + # Check that all temporary files are properly removed 1.250 + self.assertEqual(True, all([not os.path.exists(f) for f in tmp])) 1.251 + 1.252 + def test_extract(self): 1.253 + '''Test library extraction''' 1.254 + # Divert subprocess.call 1.255 + subprocess_call = subprocess.call 1.256 + extracted = {} 1.257 + def call(args, **kargs): 1.258 + if config.AR == 'lib': 1.259 + self.assertEqual(args[:2], [config.AR, '-NOLOGO']) 1.260 + self.assertTrue(args[2].startswith('-EXTRACT:')) 1.261 + extract = [args[2][len('-EXTRACT:'):]] 1.262 + self.assertTrue(extract) 1.263 + args = args[3:] 1.264 + else: 1.265 + # The command called is always AR_EXTRACT 1.266 + ar_extract = config.AR_EXTRACT.split() 1.267 + self.assertEqual(args[:len(ar_extract)], ar_extract) 1.268 + args = args[len(ar_extract):] 1.269 + # Remaining argument is always one library 1.270 + self.assertEqual(len(args), 1) 1.271 + arg = args[0] 1.272 + self.assertEqual(os.path.splitext(arg)[1], config.LIB_SUFFIX) 1.273 + # Simulate file extraction 1.274 + lib = os.path.splitext(os.path.basename(arg))[0] 1.275 + if config.AR != 'lib': 1.276 + extract = [lib, lib + '2'] 1.277 + extract = [os.path.join(kargs['cwd'], f) for f in extract] 1.278 + if config.AR != 'lib': 1.279 + extract = [Obj(f) for f in extract] 1.280 + if not lib in extracted: 1.281 + extracted[lib] = [] 1.282 + extracted[lib].extend(extract) 1.283 + self.touch(extract) 1.284 + subprocess.call = call 1.285 + 1.286 + def check_output(args, **kargs): 1.287 + # The command called is always AR 1.288 + ar = config.AR 1.289 + self.assertEqual(args[0:3], [ar, '-NOLOGO', '-LIST']) 1.290 + # Remaining argument is always one library 1.291 + self.assertRelEqual([os.path.splitext(arg)[1] for arg in args[3:]], 1.292 +[config.LIB_SUFFIX]) 1.293 + # Simulate LIB -NOLOGO -LIST 1.294 + lib = os.path.splitext(os.path.basename(args[3]))[0] 1.295 + return '%s\n%s\n' % (Obj(lib), Obj(lib + '2')) 1.296 + subprocess.check_output = check_output 1.297 + 1.298 + # ExpandArgsMore does the same as ExpandArgs 1.299 + self.touch([self.tmpfile('liby', Lib('y'))]) 1.300 + with ExpandArgsMore(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) as args: 1.301 + self.assertRelEqual(args, ['foo', '-bar'] + self.files + [self.tmpfile('liby', Lib('y'))]) 1.302 + 1.303 + # ExpandArgsMore also has an extra method extracting static libraries 1.304 + # when possible 1.305 + args.extract() 1.306 + 1.307 + files = self.files + self.liby_files + self.libx_files 1.308 + # With AR_EXTRACT, it uses the descriptors when there are, and 1.309 + # actually 1.310 + # extracts the remaining libraries 1.311 + extracted_args = [] 1.312 + for f in files: 1.313 + if f.endswith(config.LIB_SUFFIX): 1.314 + extracted_args.extend(sorted(extracted[os.path.splitext(os.path.basename(f))[0]])) 1.315 + else: 1.316 + extracted_args.append(f) 1.317 + self.assertRelEqual(args, ['foo', '-bar'] + extracted_args) 1.318 + 1.319 + tmp = args.tmp 1.320 + # Check that all temporary files are properly removed 1.321 + self.assertEqual(True, all([not os.path.exists(f) for f in tmp])) 1.322 + 1.323 + # Restore subprocess.call 1.324 + subprocess.call = subprocess_call 1.325 + 1.326 +class FakeProcess(object): 1.327 + def __init__(self, out, err = ''): 1.328 + self.out = out 1.329 + self.err = err 1.330 + 1.331 + def communicate(self): 1.332 + return (self.out, self.err) 1.333 + 1.334 +OBJDUMPS = { 1.335 +'foo.o': ''' 1.336 +00000000 g F .text\t00000001 foo 1.337 +00000000 g F .text._Z6foobarv\t00000001 _Z6foobarv 1.338 +00000000 g F .text.hello\t00000001 hello 1.339 +00000000 g F .text._ZThn4_6foobarv\t00000001 _ZThn4_6foobarv 1.340 +''', 1.341 +'bar.o': ''' 1.342 +00000000 g F .text.hi\t00000001 hi 1.343 +00000000 g F .text.hot._Z6barbazv\t00000001 .hidden _Z6barbazv 1.344 +''', 1.345 +} 1.346 + 1.347 +PRINT_ICF = ''' 1.348 +ld: ICF folding section '.text.hello' in file 'foo.o'into '.text.hi' in file 'bar.o' 1.349 +ld: ICF folding section '.foo' in file 'foo.o'into '.foo' in file 'bar.o' 1.350 +''' 1.351 + 1.352 +class SubprocessPopen(object): 1.353 + def __init__(self, test): 1.354 + self.test = test 1.355 + 1.356 + def __call__(self, args, stdout = None, stderr = None): 1.357 + self.test.assertEqual(stdout, subprocess.PIPE) 1.358 + self.test.assertEqual(stderr, subprocess.PIPE) 1.359 + if args[0] == 'objdump': 1.360 + self.test.assertEqual(args[1], '-t') 1.361 + self.test.assertTrue(args[2] in OBJDUMPS) 1.362 + return FakeProcess(OBJDUMPS[args[2]]) 1.363 + else: 1.364 + return FakeProcess('', PRINT_ICF) 1.365 + 1.366 +class TestSectionFinder(unittest.TestCase): 1.367 + def test_getSections(self): 1.368 + '''Test SectionFinder''' 1.369 + # Divert subprocess.Popen 1.370 + subprocess_popen = subprocess.Popen 1.371 + subprocess.Popen = SubprocessPopen(self) 1.372 + config.EXPAND_LIBS_ORDER_STYLE = 'linkerscript' 1.373 + config.OBJ_SUFFIX = '.o' 1.374 + config.LIB_SUFFIX = '.a' 1.375 + finder = SectionFinder(['foo.o', 'bar.o']) 1.376 + self.assertEqual(finder.getSections('foobar'), []) 1.377 + self.assertEqual(finder.getSections('_Z6barbazv'), ['.text.hot._Z6barbazv']) 1.378 + self.assertEqual(finder.getSections('_Z6foobarv'), ['.text._Z6foobarv', '.text._ZThn4_6foobarv']) 1.379 + self.assertEqual(finder.getSections('_ZThn4_6foobarv'), ['.text._Z6foobarv', '.text._ZThn4_6foobarv']) 1.380 + subprocess.Popen = subprocess_popen 1.381 + 1.382 +class TestSymbolOrder(unittest.TestCase): 1.383 + def test_getOrderedSections(self): 1.384 + '''Test ExpandMoreArgs' _getOrderedSections''' 1.385 + # Divert subprocess.Popen 1.386 + subprocess_popen = subprocess.Popen 1.387 + subprocess.Popen = SubprocessPopen(self) 1.388 + config.EXPAND_LIBS_ORDER_STYLE = 'linkerscript' 1.389 + config.OBJ_SUFFIX = '.o' 1.390 + config.LIB_SUFFIX = '.a' 1.391 + config.LD_PRINT_ICF_SECTIONS = '' 1.392 + args = ExpandArgsMore(['foo', '-bar', 'bar.o', 'foo.o']) 1.393 + self.assertEqual(args._getOrderedSections(['_Z6foobarv', '_Z6barbazv']), ['.text._Z6foobarv', '.text._ZThn4_6foobarv', '.text.hot._Z6barbazv']) 1.394 + self.assertEqual(args._getOrderedSections(['_ZThn4_6foobarv', '_Z6barbazv']), ['.text._Z6foobarv', '.text._ZThn4_6foobarv', '.text.hot._Z6barbazv']) 1.395 + subprocess.Popen = subprocess_popen 1.396 + 1.397 + def test_getFoldedSections(self): 1.398 + '''Test ExpandMoreArgs' _getFoldedSections''' 1.399 + # Divert subprocess.Popen 1.400 + subprocess_popen = subprocess.Popen 1.401 + subprocess.Popen = SubprocessPopen(self) 1.402 + config.LD_PRINT_ICF_SECTIONS = '-Wl,--print-icf-sections' 1.403 + args = ExpandArgsMore(['foo', '-bar', 'bar.o', 'foo.o']) 1.404 + self.assertEqual(args._getFoldedSections(), {'.text.hello': ['.text.hi'], '.text.hi': ['.text.hello']}) 1.405 + subprocess.Popen = subprocess_popen 1.406 + 1.407 + def test_getOrderedSectionsWithICF(self): 1.408 + '''Test ExpandMoreArgs' _getOrderedSections, with ICF''' 1.409 + # Divert subprocess.Popen 1.410 + subprocess_popen = subprocess.Popen 1.411 + subprocess.Popen = SubprocessPopen(self) 1.412 + config.EXPAND_LIBS_ORDER_STYLE = 'linkerscript' 1.413 + config.OBJ_SUFFIX = '.o' 1.414 + config.LIB_SUFFIX = '.a' 1.415 + config.LD_PRINT_ICF_SECTIONS = '-Wl,--print-icf-sections' 1.416 + args = ExpandArgsMore(['foo', '-bar', 'bar.o', 'foo.o']) 1.417 + self.assertEqual(args._getOrderedSections(['hello', '_Z6barbazv']), ['.text.hello', '.text.hi', '.text.hot._Z6barbazv']) 1.418 + self.assertEqual(args._getOrderedSections(['_ZThn4_6foobarv', 'hi', '_Z6barbazv']), ['.text._Z6foobarv', '.text._ZThn4_6foobarv', '.text.hi', '.text.hello', '.text.hot._Z6barbazv']) 1.419 + subprocess.Popen = subprocess_popen 1.420 + 1.421 + 1.422 +if __name__ == '__main__': 1.423 + mozunit.main()