michael@0: import subprocess michael@0: import unittest michael@0: import sys michael@0: import os michael@0: import imp michael@0: from tempfile import mkdtemp michael@0: from shutil import rmtree michael@0: import mozunit michael@0: michael@0: from UserString import UserString michael@0: # Create a controlled configuration for use by expandlibs michael@0: config_win = { michael@0: 'AR': 'lib', michael@0: 'AR_EXTRACT': '', michael@0: 'DLL_PREFIX': '', michael@0: 'LIB_PREFIX': '', michael@0: 'OBJ_SUFFIX': '.obj', michael@0: 'LIB_SUFFIX': '.lib', michael@0: 'DLL_SUFFIX': '.dll', michael@0: 'IMPORT_LIB_SUFFIX': '.lib', michael@0: 'LIBS_DESC_SUFFIX': '.desc', michael@0: 'EXPAND_LIBS_LIST_STYLE': 'list', michael@0: } michael@0: config_unix = { michael@0: 'AR': 'ar', michael@0: 'AR_EXTRACT': 'ar -x', michael@0: 'DLL_PREFIX': 'lib', michael@0: 'LIB_PREFIX': 'lib', michael@0: 'OBJ_SUFFIX': '.o', michael@0: 'LIB_SUFFIX': '.a', michael@0: 'DLL_SUFFIX': '.so', michael@0: 'IMPORT_LIB_SUFFIX': '', michael@0: 'LIBS_DESC_SUFFIX': '.desc', michael@0: 'EXPAND_LIBS_LIST_STYLE': 'linkerscript', michael@0: } michael@0: michael@0: config = sys.modules['expandlibs_config'] = imp.new_module('expandlibs_config') michael@0: michael@0: from expandlibs import LibDescriptor, ExpandArgs, relativize, ExpandLibsDeps michael@0: from expandlibs_gen import generate michael@0: from expandlibs_exec import ExpandArgsMore, SectionFinder michael@0: michael@0: def Lib(name): michael@0: return config.LIB_PREFIX + name + config.LIB_SUFFIX michael@0: michael@0: def Obj(name): michael@0: return name + config.OBJ_SUFFIX michael@0: michael@0: def Dll(name): michael@0: return config.DLL_PREFIX + name + config.DLL_SUFFIX michael@0: michael@0: def ImportLib(name): michael@0: if not len(config.IMPORT_LIB_SUFFIX): return Dll(name) michael@0: return config.LIB_PREFIX + name + config.IMPORT_LIB_SUFFIX michael@0: michael@0: class TestRelativize(unittest.TestCase): michael@0: def test_relativize(self): michael@0: '''Test relativize()''' michael@0: os_path_exists = os.path.exists michael@0: def exists(path): michael@0: return True michael@0: os.path.exists = exists michael@0: self.assertEqual(relativize(os.path.abspath(os.curdir)), os.curdir) michael@0: self.assertEqual(relativize(os.path.abspath(os.pardir)), os.pardir) michael@0: self.assertEqual(relativize(os.path.join(os.curdir, 'a')), 'a') michael@0: self.assertEqual(relativize(os.path.join(os.path.abspath(os.curdir), 'a')), 'a') michael@0: # relativize is expected to return the absolute path if it is shorter michael@0: self.assertEqual(relativize(os.sep), os.sep) michael@0: os.path.exists = os.path.exists michael@0: michael@0: class TestLibDescriptor(unittest.TestCase): michael@0: def test_serialize(self): michael@0: '''Test LibDescriptor's serialization''' michael@0: desc = LibDescriptor() michael@0: desc[LibDescriptor.KEYS[0]] = ['a', 'b'] michael@0: self.assertEqual(str(desc), "{0} = a b".format(LibDescriptor.KEYS[0])) michael@0: desc['unsupported-key'] = ['a'] michael@0: self.assertEqual(str(desc), "{0} = a b".format(LibDescriptor.KEYS[0])) michael@0: desc[LibDescriptor.KEYS[1]] = ['c', 'd', 'e'] michael@0: self.assertEqual(str(desc), michael@0: "{0} = a b\n{1} = c d e" michael@0: .format(LibDescriptor.KEYS[0], LibDescriptor.KEYS[1])) michael@0: desc[LibDescriptor.KEYS[0]] = [] michael@0: self.assertEqual(str(desc), "{0} = c d e".format(LibDescriptor.KEYS[1])) michael@0: michael@0: def test_read(self): michael@0: '''Test LibDescriptor's initialization''' michael@0: desc_list = ["# Comment", michael@0: "{0} = a b".format(LibDescriptor.KEYS[1]), michael@0: "", # Empty line michael@0: "foo = bar", # Should be discarded michael@0: "{0} = c d e".format(LibDescriptor.KEYS[0])] michael@0: desc = LibDescriptor(desc_list) michael@0: self.assertEqual(desc[LibDescriptor.KEYS[1]], ['a', 'b']) michael@0: self.assertEqual(desc[LibDescriptor.KEYS[0]], ['c', 'd', 'e']) michael@0: self.assertEqual(False, 'foo' in desc) michael@0: michael@0: def wrap_method(conf, wrapped_method): michael@0: '''Wrapper used to call a test with a specific configuration''' michael@0: def _method(self): michael@0: for key in conf: michael@0: setattr(config, key, conf[key]) michael@0: self.init() michael@0: try: michael@0: wrapped_method(self) michael@0: except: michael@0: raise michael@0: finally: michael@0: self.cleanup() michael@0: return _method michael@0: michael@0: class ReplicateTests(type): michael@0: '''Replicates tests for unix and windows variants''' michael@0: def __new__(cls, clsName, bases, dict): michael@0: for name in [key for key in dict if key.startswith('test_')]: michael@0: dict[name + '_unix'] = wrap_method(config_unix, dict[name]) michael@0: dict[name + '_unix'].__doc__ = dict[name].__doc__ + ' (unix)' michael@0: dict[name + '_win'] = wrap_method(config_win, dict[name]) michael@0: dict[name + '_win'].__doc__ = dict[name].__doc__ + ' (win)' michael@0: del dict[name] michael@0: return type.__new__(cls, clsName, bases, dict) michael@0: michael@0: class TestCaseWithTmpDir(unittest.TestCase): michael@0: __metaclass__ = ReplicateTests michael@0: def init(self): michael@0: self.tmpdir = os.path.abspath(mkdtemp(dir=os.curdir)) michael@0: michael@0: def cleanup(self): michael@0: rmtree(self.tmpdir) michael@0: michael@0: def touch(self, files): michael@0: for f in files: michael@0: open(f, 'w').close() michael@0: michael@0: def tmpfile(self, *args): michael@0: return os.path.join(self.tmpdir, *args) michael@0: michael@0: class TestExpandLibsGen(TestCaseWithTmpDir): michael@0: def test_generate(self): michael@0: '''Test library descriptor generation''' michael@0: files = [self.tmpfile(f) for f in michael@0: [Lib('a'), Obj('b'), Lib('c'), Obj('d'), Obj('e'), Lib('f')]] michael@0: self.touch(files[:-1]) michael@0: self.touch([files[-1] + config.LIBS_DESC_SUFFIX]) michael@0: michael@0: desc = generate(files) michael@0: self.assertEqual(desc['OBJS'], [self.tmpfile(Obj(s)) for s in ['b', 'd', 'e']]) michael@0: self.assertEqual(desc['LIBS'], [self.tmpfile(Lib(s)) for s in ['a', 'c', 'f']]) michael@0: michael@0: self.assertRaises(Exception, generate, files + [self.tmpfile(Obj('z'))]) michael@0: self.assertRaises(Exception, generate, files + [self.tmpfile(Lib('y'))]) michael@0: michael@0: class TestExpandInit(TestCaseWithTmpDir): michael@0: def init(self): michael@0: ''' Initializes test environment for library expansion tests''' michael@0: super(TestExpandInit, self).init() michael@0: # Create 2 fake libraries, each containing 3 objects, and the second michael@0: # including the first one and another library. michael@0: os.mkdir(self.tmpfile('libx')) michael@0: os.mkdir(self.tmpfile('liby')) michael@0: self.libx_files = [self.tmpfile('libx', Obj(f)) for f in ['g', 'h', 'i']] michael@0: self.liby_files = [self.tmpfile('liby', Obj(f)) for f in ['j', 'k', 'l']] + [self.tmpfile('liby', Lib('z'))] michael@0: self.touch(self.libx_files + self.liby_files) michael@0: with open(self.tmpfile('libx', Lib('x') + config.LIBS_DESC_SUFFIX), 'w') as f: michael@0: f.write(str(generate(self.libx_files))) michael@0: with open(self.tmpfile('liby', Lib('y') + config.LIBS_DESC_SUFFIX), 'w') as f: michael@0: f.write(str(generate(self.liby_files + [self.tmpfile('libx', Lib('x'))]))) michael@0: michael@0: # Create various objects and libraries michael@0: self.arg_files = [self.tmpfile(f) for f in [Lib('a'), Obj('b'), Obj('c'), Lib('d'), Obj('e')]] michael@0: # We always give library names (LIB_PREFIX/SUFFIX), even for michael@0: # dynamic/import libraries michael@0: self.files = self.arg_files + [self.tmpfile(ImportLib('f'))] michael@0: self.arg_files += [self.tmpfile(Lib('f'))] michael@0: self.touch(self.files) michael@0: michael@0: def assertRelEqual(self, args1, args2): michael@0: self.assertEqual(args1, [relativize(a) for a in args2]) michael@0: michael@0: class TestExpandArgs(TestExpandInit): michael@0: def test_expand(self): michael@0: '''Test library expansion''' michael@0: # Expanding arguments means libraries with a descriptor are expanded michael@0: # with the descriptor content, and import libraries are used when michael@0: # a library doesn't exist michael@0: args = ExpandArgs(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) michael@0: self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + self.libx_files) michael@0: michael@0: # When a library exists at the same time as a descriptor, we just use michael@0: # the library michael@0: self.touch([self.tmpfile('libx', Lib('x'))]) michael@0: args = ExpandArgs(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) michael@0: self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + [self.tmpfile('libx', Lib('x'))]) michael@0: michael@0: self.touch([self.tmpfile('liby', Lib('y'))]) michael@0: args = ExpandArgs(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) michael@0: self.assertRelEqual(args, ['foo', '-bar'] + self.files + [self.tmpfile('liby', Lib('y'))]) michael@0: michael@0: class TestExpandLibsDeps(TestExpandInit): michael@0: def test_expandlibsdeps(self): michael@0: '''Test library expansion for dependencies''' michael@0: # Dependency list for a library with a descriptor is equivalent to michael@0: # the arguments expansion, to which we add each descriptor michael@0: args = self.arg_files + [self.tmpfile('liby', Lib('y'))] michael@0: self.assertRelEqual(ExpandLibsDeps(args), ExpandArgs(args) + [self.tmpfile('libx', Lib('x') + config.LIBS_DESC_SUFFIX), self.tmpfile('liby', Lib('y') + config.LIBS_DESC_SUFFIX)]) michael@0: michael@0: # When a library exists at the same time as a descriptor, the michael@0: # descriptor is not a dependency michael@0: self.touch([self.tmpfile('libx', Lib('x'))]) michael@0: args = self.arg_files + [self.tmpfile('liby', Lib('y'))] michael@0: self.assertRelEqual(ExpandLibsDeps(args), ExpandArgs(args) + [self.tmpfile('liby', Lib('y') + config.LIBS_DESC_SUFFIX)]) michael@0: michael@0: self.touch([self.tmpfile('liby', Lib('y'))]) michael@0: args = self.arg_files + [self.tmpfile('liby', Lib('y'))] michael@0: self.assertRelEqual(ExpandLibsDeps(args), ExpandArgs(args)) michael@0: michael@0: class TestExpandArgsMore(TestExpandInit): michael@0: def test_makelist(self): michael@0: '''Test grouping object files in lists''' michael@0: # ExpandArgsMore does the same as ExpandArgs michael@0: with ExpandArgsMore(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) as args: michael@0: self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + self.libx_files) michael@0: michael@0: # But also has an extra method replacing object files with a list michael@0: args.makelist() michael@0: # self.files has objects at #1, #2, #4 michael@0: self.assertRelEqual(args[:3], ['foo', '-bar'] + self.files[:1]) michael@0: self.assertRelEqual(args[4:], [self.files[3]] + self.files[5:] + [self.tmpfile('liby', Lib('z'))]) michael@0: michael@0: # Check the list file content michael@0: objs = [f for f in self.files + self.liby_files + self.libx_files if f.endswith(config.OBJ_SUFFIX)] michael@0: if config.EXPAND_LIBS_LIST_STYLE == "linkerscript": michael@0: self.assertNotEqual(args[3][0], '@') michael@0: filename = args[3] michael@0: content = ['INPUT("{0}")'.format(relativize(f)) for f in objs] michael@0: with open(filename, 'r') as f: michael@0: self.assertEqual([l.strip() for l in f.readlines() if len(l.strip())], content) michael@0: elif config.EXPAND_LIBS_LIST_STYLE == "list": michael@0: self.assertEqual(args[3][0], '@') michael@0: filename = args[3][1:] michael@0: content = objs michael@0: with open(filename, 'r') as f: michael@0: self.assertRelEqual([l.strip() for l in f.readlines() if len(l.strip())], content) michael@0: michael@0: tmp = args.tmp michael@0: # Check that all temporary files are properly removed michael@0: self.assertEqual(True, all([not os.path.exists(f) for f in tmp])) michael@0: michael@0: def test_extract(self): michael@0: '''Test library extraction''' michael@0: # Divert subprocess.call michael@0: subprocess_call = subprocess.call michael@0: extracted = {} michael@0: def call(args, **kargs): michael@0: if config.AR == 'lib': michael@0: self.assertEqual(args[:2], [config.AR, '-NOLOGO']) michael@0: self.assertTrue(args[2].startswith('-EXTRACT:')) michael@0: extract = [args[2][len('-EXTRACT:'):]] michael@0: self.assertTrue(extract) michael@0: args = args[3:] michael@0: else: michael@0: # The command called is always AR_EXTRACT michael@0: ar_extract = config.AR_EXTRACT.split() michael@0: self.assertEqual(args[:len(ar_extract)], ar_extract) michael@0: args = args[len(ar_extract):] michael@0: # Remaining argument is always one library michael@0: self.assertEqual(len(args), 1) michael@0: arg = args[0] michael@0: self.assertEqual(os.path.splitext(arg)[1], config.LIB_SUFFIX) michael@0: # Simulate file extraction michael@0: lib = os.path.splitext(os.path.basename(arg))[0] michael@0: if config.AR != 'lib': michael@0: extract = [lib, lib + '2'] michael@0: extract = [os.path.join(kargs['cwd'], f) for f in extract] michael@0: if config.AR != 'lib': michael@0: extract = [Obj(f) for f in extract] michael@0: if not lib in extracted: michael@0: extracted[lib] = [] michael@0: extracted[lib].extend(extract) michael@0: self.touch(extract) michael@0: subprocess.call = call michael@0: michael@0: def check_output(args, **kargs): michael@0: # The command called is always AR michael@0: ar = config.AR michael@0: self.assertEqual(args[0:3], [ar, '-NOLOGO', '-LIST']) michael@0: # Remaining argument is always one library michael@0: self.assertRelEqual([os.path.splitext(arg)[1] for arg in args[3:]], michael@0: [config.LIB_SUFFIX]) michael@0: # Simulate LIB -NOLOGO -LIST michael@0: lib = os.path.splitext(os.path.basename(args[3]))[0] michael@0: return '%s\n%s\n' % (Obj(lib), Obj(lib + '2')) michael@0: subprocess.check_output = check_output michael@0: michael@0: # ExpandArgsMore does the same as ExpandArgs michael@0: self.touch([self.tmpfile('liby', Lib('y'))]) michael@0: with ExpandArgsMore(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) as args: michael@0: self.assertRelEqual(args, ['foo', '-bar'] + self.files + [self.tmpfile('liby', Lib('y'))]) michael@0: michael@0: # ExpandArgsMore also has an extra method extracting static libraries michael@0: # when possible michael@0: args.extract() michael@0: michael@0: files = self.files + self.liby_files + self.libx_files michael@0: # With AR_EXTRACT, it uses the descriptors when there are, and michael@0: # actually michael@0: # extracts the remaining libraries michael@0: extracted_args = [] michael@0: for f in files: michael@0: if f.endswith(config.LIB_SUFFIX): michael@0: extracted_args.extend(sorted(extracted[os.path.splitext(os.path.basename(f))[0]])) michael@0: else: michael@0: extracted_args.append(f) michael@0: self.assertRelEqual(args, ['foo', '-bar'] + extracted_args) michael@0: michael@0: tmp = args.tmp michael@0: # Check that all temporary files are properly removed michael@0: self.assertEqual(True, all([not os.path.exists(f) for f in tmp])) michael@0: michael@0: # Restore subprocess.call michael@0: subprocess.call = subprocess_call michael@0: michael@0: class FakeProcess(object): michael@0: def __init__(self, out, err = ''): michael@0: self.out = out michael@0: self.err = err michael@0: michael@0: def communicate(self): michael@0: return (self.out, self.err) michael@0: michael@0: OBJDUMPS = { michael@0: 'foo.o': ''' michael@0: 00000000 g F .text\t00000001 foo michael@0: 00000000 g F .text._Z6foobarv\t00000001 _Z6foobarv michael@0: 00000000 g F .text.hello\t00000001 hello michael@0: 00000000 g F .text._ZThn4_6foobarv\t00000001 _ZThn4_6foobarv michael@0: ''', michael@0: 'bar.o': ''' michael@0: 00000000 g F .text.hi\t00000001 hi michael@0: 00000000 g F .text.hot._Z6barbazv\t00000001 .hidden _Z6barbazv michael@0: ''', michael@0: } michael@0: michael@0: PRINT_ICF = ''' michael@0: ld: ICF folding section '.text.hello' in file 'foo.o'into '.text.hi' in file 'bar.o' michael@0: ld: ICF folding section '.foo' in file 'foo.o'into '.foo' in file 'bar.o' michael@0: ''' michael@0: michael@0: class SubprocessPopen(object): michael@0: def __init__(self, test): michael@0: self.test = test michael@0: michael@0: def __call__(self, args, stdout = None, stderr = None): michael@0: self.test.assertEqual(stdout, subprocess.PIPE) michael@0: self.test.assertEqual(stderr, subprocess.PIPE) michael@0: if args[0] == 'objdump': michael@0: self.test.assertEqual(args[1], '-t') michael@0: self.test.assertTrue(args[2] in OBJDUMPS) michael@0: return FakeProcess(OBJDUMPS[args[2]]) michael@0: else: michael@0: return FakeProcess('', PRINT_ICF) michael@0: michael@0: class TestSectionFinder(unittest.TestCase): michael@0: def test_getSections(self): michael@0: '''Test SectionFinder''' michael@0: # Divert subprocess.Popen michael@0: subprocess_popen = subprocess.Popen michael@0: subprocess.Popen = SubprocessPopen(self) michael@0: config.EXPAND_LIBS_ORDER_STYLE = 'linkerscript' michael@0: config.OBJ_SUFFIX = '.o' michael@0: config.LIB_SUFFIX = '.a' michael@0: finder = SectionFinder(['foo.o', 'bar.o']) michael@0: self.assertEqual(finder.getSections('foobar'), []) michael@0: self.assertEqual(finder.getSections('_Z6barbazv'), ['.text.hot._Z6barbazv']) michael@0: self.assertEqual(finder.getSections('_Z6foobarv'), ['.text._Z6foobarv', '.text._ZThn4_6foobarv']) michael@0: self.assertEqual(finder.getSections('_ZThn4_6foobarv'), ['.text._Z6foobarv', '.text._ZThn4_6foobarv']) michael@0: subprocess.Popen = subprocess_popen michael@0: michael@0: class TestSymbolOrder(unittest.TestCase): michael@0: def test_getOrderedSections(self): michael@0: '''Test ExpandMoreArgs' _getOrderedSections''' michael@0: # Divert subprocess.Popen michael@0: subprocess_popen = subprocess.Popen michael@0: subprocess.Popen = SubprocessPopen(self) michael@0: config.EXPAND_LIBS_ORDER_STYLE = 'linkerscript' michael@0: config.OBJ_SUFFIX = '.o' michael@0: config.LIB_SUFFIX = '.a' michael@0: config.LD_PRINT_ICF_SECTIONS = '' michael@0: args = ExpandArgsMore(['foo', '-bar', 'bar.o', 'foo.o']) michael@0: self.assertEqual(args._getOrderedSections(['_Z6foobarv', '_Z6barbazv']), ['.text._Z6foobarv', '.text._ZThn4_6foobarv', '.text.hot._Z6barbazv']) michael@0: self.assertEqual(args._getOrderedSections(['_ZThn4_6foobarv', '_Z6barbazv']), ['.text._Z6foobarv', '.text._ZThn4_6foobarv', '.text.hot._Z6barbazv']) michael@0: subprocess.Popen = subprocess_popen michael@0: michael@0: def test_getFoldedSections(self): michael@0: '''Test ExpandMoreArgs' _getFoldedSections''' michael@0: # Divert subprocess.Popen michael@0: subprocess_popen = subprocess.Popen michael@0: subprocess.Popen = SubprocessPopen(self) michael@0: config.LD_PRINT_ICF_SECTIONS = '-Wl,--print-icf-sections' michael@0: args = ExpandArgsMore(['foo', '-bar', 'bar.o', 'foo.o']) michael@0: self.assertEqual(args._getFoldedSections(), {'.text.hello': ['.text.hi'], '.text.hi': ['.text.hello']}) michael@0: subprocess.Popen = subprocess_popen michael@0: michael@0: def test_getOrderedSectionsWithICF(self): michael@0: '''Test ExpandMoreArgs' _getOrderedSections, with ICF''' michael@0: # Divert subprocess.Popen michael@0: subprocess_popen = subprocess.Popen michael@0: subprocess.Popen = SubprocessPopen(self) michael@0: config.EXPAND_LIBS_ORDER_STYLE = 'linkerscript' michael@0: config.OBJ_SUFFIX = '.o' michael@0: config.LIB_SUFFIX = '.a' michael@0: config.LD_PRINT_ICF_SECTIONS = '-Wl,--print-icf-sections' michael@0: args = ExpandArgsMore(['foo', '-bar', 'bar.o', 'foo.o']) michael@0: self.assertEqual(args._getOrderedSections(['hello', '_Z6barbazv']), ['.text.hello', '.text.hi', '.text.hot._Z6barbazv']) michael@0: self.assertEqual(args._getOrderedSections(['_ZThn4_6foobarv', 'hi', '_Z6barbazv']), ['.text._Z6foobarv', '.text._ZThn4_6foobarv', '.text.hi', '.text.hello', '.text.hot._Z6barbazv']) michael@0: subprocess.Popen = subprocess_popen michael@0: michael@0: michael@0: if __name__ == '__main__': michael@0: mozunit.main()