config/tests/unit-expandlibs.py

Fri, 16 Jan 2015 18:13:44 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 18:13:44 +0100
branch
TOR_BUG_9701
changeset 14
925c144e1f1f
permissions
-rw-r--r--

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()

mercurial