1
from __future__ import with_statement
7
from tempfile import mkdtemp
8
from shutil import rmtree
11
from UserString import UserString
12
# Create a controlled configuration for use by expandlibs
20
'IMPORT_LIB_SUFFIX': '.lib',
21
'LIBS_DESC_SUFFIX': '.desc',
22
'EXPAND_LIBS_LIST_STYLE': 'list',
25
'AR_EXTRACT': 'ar -x',
31
'IMPORT_LIB_SUFFIX': '',
32
'LIBS_DESC_SUFFIX': '.desc',
33
'EXPAND_LIBS_LIST_STYLE': 'linkerscript',
36
config = sys.modules['expandlibs_config'] = imp.new_module('expandlibs_config')
38
from expandlibs import LibDescriptor, ExpandArgs, relativize, ExpandLibsDeps
39
from expandlibs_gen import generate
40
from expandlibs_exec import ExpandArgsMore, SectionFinder
43
return config.LIB_PREFIX + name + config.LIB_SUFFIX
46
return name + config.OBJ_SUFFIX
49
return config.DLL_PREFIX + name + config.DLL_SUFFIX
52
if not len(config.IMPORT_LIB_SUFFIX): return Dll(name)
53
return config.LIB_PREFIX + name + config.IMPORT_LIB_SUFFIX
55
class TestRelativize(unittest.TestCase):
56
def test_relativize(self):
57
'''Test relativize()'''
58
os_path_exists = os.path.exists
61
os.path.exists = exists
62
self.assertEqual(relativize(os.path.abspath(os.curdir)), os.curdir)
63
self.assertEqual(relativize(os.path.abspath(os.pardir)), os.pardir)
64
self.assertEqual(relativize(os.path.join(os.curdir, 'a')), 'a')
65
self.assertEqual(relativize(os.path.join(os.path.abspath(os.curdir), 'a')), 'a')
66
# relativize is expected to return the absolute path if it is shorter
67
self.assertEqual(relativize(os.sep), os.sep)
68
os.path.exists = os.path.exists
70
class TestLibDescriptor(unittest.TestCase):
71
def test_serialize(self):
72
'''Test LibDescriptor's serialization'''
73
desc = LibDescriptor()
74
desc[LibDescriptor.KEYS[0]] = ['a', 'b']
75
self.assertEqual(str(desc), "%s = a b" % LibDescriptor.KEYS[0])
76
desc['unsupported-key'] = ['a']
77
self.assertEqual(str(desc), "%s = a b" % LibDescriptor.KEYS[0])
78
desc[LibDescriptor.KEYS[1]] = ['c', 'd', 'e']
79
self.assertEqual(str(desc), "%s = a b\n%s = c d e" % (LibDescriptor.KEYS[0], LibDescriptor.KEYS[1]))
80
desc[LibDescriptor.KEYS[0]] = []
81
self.assertEqual(str(desc), "%s = c d e" % (LibDescriptor.KEYS[1]))
84
'''Test LibDescriptor's initialization'''
85
desc_list = ["# Comment",
86
"%s = a b" % LibDescriptor.KEYS[1],
88
"foo = bar", # Should be discarded
89
"%s = c d e" % LibDescriptor.KEYS[0]]
90
desc = LibDescriptor(desc_list)
91
self.assertEqual(desc[LibDescriptor.KEYS[1]], ['a', 'b'])
92
self.assertEqual(desc[LibDescriptor.KEYS[0]], ['c', 'd', 'e'])
93
self.assertEqual(False, 'foo' in desc)
95
def wrap_method(conf, wrapped_method):
96
'''Wrapper used to call a test with a specific configuration'''
99
setattr(config, key, conf[key])
109
class ReplicateTests(type):
110
'''Replicates tests for unix and windows variants'''
111
def __new__(cls, clsName, bases, dict):
112
for name in [key for key in dict if key.startswith('test_')]:
113
dict[name + '_unix'] = wrap_method(config_unix, dict[name])
114
dict[name + '_unix'].__doc__ = dict[name].__doc__ + ' (unix)'
115
dict[name + '_win'] = wrap_method(config_win, dict[name])
116
dict[name + '_win'].__doc__ = dict[name].__doc__ + ' (win)'
118
return type.__new__(cls, clsName, bases, dict)
120
class TestCaseWithTmpDir(unittest.TestCase):
121
__metaclass__ = ReplicateTests
123
self.tmpdir = os.path.abspath(mkdtemp(dir=os.curdir))
128
def touch(self, files):
132
def tmpfile(self, *args):
133
return os.path.join(self.tmpdir, *args)
135
class TestExpandLibsGen(TestCaseWithTmpDir):
136
def test_generate(self):
137
'''Test library descriptor generation'''
138
files = [self.tmpfile(f) for f in
139
[Lib('a'), Obj('b'), Lib('c'), Obj('d'), Obj('e'), Lib('f')]]
140
self.touch(files[:-1])
141
self.touch([files[-1] + config.LIBS_DESC_SUFFIX])
143
desc = generate(files)
144
self.assertEqual(desc['OBJS'], [self.tmpfile(Obj(s)) for s in ['b', 'd', 'e']])
145
self.assertEqual(desc['LIBS'], [self.tmpfile(Lib(s)) for s in ['a', 'c', 'f']])
147
self.assertRaises(Exception, generate, files + [self.tmpfile(Obj('z'))])
148
self.assertRaises(Exception, generate, files + [self.tmpfile(Lib('y'))])
150
class TestExpandInit(TestCaseWithTmpDir):
152
''' Initializes test environment for library expansion tests'''
153
super(TestExpandInit, self).init()
154
# Create 2 fake libraries, each containing 3 objects, and the second
155
# including the first one and another library.
156
os.mkdir(self.tmpfile('libx'))
157
os.mkdir(self.tmpfile('liby'))
158
self.libx_files = [self.tmpfile('libx', Obj(f)) for f in ['g', 'h', 'i']]
159
self.liby_files = [self.tmpfile('liby', Obj(f)) for f in ['j', 'k', 'l']] + [self.tmpfile('liby', Lib('z'))]
160
self.touch(self.libx_files + self.liby_files)
161
with open(self.tmpfile('libx', Lib('x') + config.LIBS_DESC_SUFFIX), 'w') as f:
162
f.write(str(generate(self.libx_files)))
163
with open(self.tmpfile('liby', Lib('y') + config.LIBS_DESC_SUFFIX), 'w') as f:
164
f.write(str(generate(self.liby_files + [self.tmpfile('libx', Lib('x'))])))
166
# Create various objects and libraries
167
self.arg_files = [self.tmpfile(f) for f in [Lib('a'), Obj('b'), Obj('c'), Lib('d'), Obj('e')]]
168
# We always give library names (LIB_PREFIX/SUFFIX), even for
169
# dynamic/import libraries
170
self.files = self.arg_files + [self.tmpfile(ImportLib('f'))]
171
self.arg_files += [self.tmpfile(Lib('f'))]
172
self.touch(self.files)
174
def assertRelEqual(self, args1, args2):
175
self.assertEqual(args1, [relativize(a) for a in args2])
177
class TestExpandArgs(TestExpandInit):
178
def test_expand(self):
179
'''Test library expansion'''
180
# Expanding arguments means libraries with a descriptor are expanded
181
# with the descriptor content, and import libraries are used when
182
# a library doesn't exist
183
args = ExpandArgs(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))])
184
self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + self.libx_files)
186
# When a library exists at the same time as a descriptor, we just use
188
self.touch([self.tmpfile('libx', Lib('x'))])
189
args = ExpandArgs(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))])
190
self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + [self.tmpfile('libx', Lib('x'))])
192
self.touch([self.tmpfile('liby', Lib('y'))])
193
args = ExpandArgs(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))])
194
self.assertRelEqual(args, ['foo', '-bar'] + self.files + [self.tmpfile('liby', Lib('y'))])
196
class TestExpandLibsDeps(TestExpandInit):
197
def test_expandlibsdeps(self):
198
'''Test library expansion for dependencies'''
199
# Dependency list for a library with a descriptor is equivalent to
200
# the arguments expansion, to which we add each descriptor
201
args = self.arg_files + [self.tmpfile('liby', Lib('y'))]
202
self.assertRelEqual(ExpandLibsDeps(args), ExpandArgs(args) + [self.tmpfile('libx', Lib('x') + config.LIBS_DESC_SUFFIX), self.tmpfile('liby', Lib('y') + config.LIBS_DESC_SUFFIX)])
204
# When a library exists at the same time as a descriptor, the
205
# descriptor is not a dependency
206
self.touch([self.tmpfile('libx', Lib('x'))])
207
args = self.arg_files + [self.tmpfile('liby', Lib('y'))]
208
self.assertRelEqual(ExpandLibsDeps(args), ExpandArgs(args) + [self.tmpfile('liby', Lib('y') + config.LIBS_DESC_SUFFIX)])
210
self.touch([self.tmpfile('liby', Lib('y'))])
211
args = self.arg_files + [self.tmpfile('liby', Lib('y'))]
212
self.assertRelEqual(ExpandLibsDeps(args), ExpandArgs(args))
214
class TestExpandArgsMore(TestExpandInit):
215
def test_makelist(self):
216
'''Test grouping object files in lists'''
217
# ExpandArgsMore does the same as ExpandArgs
218
with ExpandArgsMore(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) as args:
219
self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + self.libx_files)
221
# But also has an extra method replacing object files with a list
223
# self.files has objects at #1, #2, #4
224
self.assertRelEqual(args[:3], ['foo', '-bar'] + self.files[:1])
225
self.assertRelEqual(args[4:], [self.files[3]] + self.files[5:] + [self.tmpfile('liby', Lib('z'))])
227
# Check the list file content
228
objs = [f for f in self.files + self.liby_files + self.libx_files if f.endswith(config.OBJ_SUFFIX)]
229
if config.EXPAND_LIBS_LIST_STYLE == "linkerscript":
230
self.assertNotEqual(args[3][0], '@')
232
content = ['INPUT("%s")' % relativize(f) for f in objs]
233
with open(filename, 'r') as f:
234
self.assertEqual([l.strip() for l in f.readlines() if len(l.strip())], content)
235
elif config.EXPAND_LIBS_LIST_STYLE == "list":
236
self.assertEqual(args[3][0], '@')
237
filename = args[3][1:]
239
with open(filename, 'r') as f:
240
self.assertRelEqual([l.strip() for l in f.readlines() if len(l.strip())], content)
243
# Check that all temporary files are properly removed
244
self.assertEqual(True, all([not os.path.exists(f) for f in tmp]))
246
def test_extract(self):
247
'''Test library extraction'''
248
# Divert subprocess.call
249
subprocess_call = subprocess.call
251
def call(args, **kargs):
252
# The command called is always AR_EXTRACT
253
ar_extract = config.AR_EXTRACT.split()
254
self.assertRelEqual(args[:len(ar_extract)], ar_extract)
255
# Remaining argument is always one library
256
self.assertRelEqual([os.path.splitext(arg)[1] for arg in args[len(ar_extract):]], [config.LIB_SUFFIX])
257
# Simulate AR_EXTRACT extracting one object file for the library
258
lib = os.path.splitext(os.path.basename(args[len(ar_extract)]))[0]
259
extracted[lib] = os.path.join(kargs['cwd'], "%s" % Obj(lib))
260
self.touch([extracted[lib]])
261
subprocess.call = call
263
# ExpandArgsMore does the same as ExpandArgs
264
self.touch([self.tmpfile('liby', Lib('y'))])
265
with ExpandArgsMore(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) as args:
266
self.assertRelEqual(args, ['foo', '-bar'] + self.files + [self.tmpfile('liby', Lib('y'))])
268
# ExpandArgsMore also has an extra method extracting static libraries
272
files = self.files + self.liby_files + self.libx_files
273
if not len(config.AR_EXTRACT):
274
# If we don't have an AR_EXTRACT, extract() expands libraries with a
275
# descriptor when the corresponding library exists (which ExpandArgs
277
self.assertRelEqual(args, ['foo', '-bar'] + files)
279
# With AR_EXTRACT, it uses the descriptors when there are, and actually
280
# extracts the remaining libraries
281
self.assertRelEqual(args, ['foo', '-bar'] + [extracted[os.path.splitext(os.path.basename(f))[0]] if f.endswith(config.LIB_SUFFIX) else f for f in files])
284
# Check that all temporary files are properly removed
285
self.assertEqual(True, all([not os.path.exists(f) for f in tmp]))
287
# Restore subprocess.call
288
subprocess.call = subprocess_call
290
class FakeProcess(object):
291
def __init__(self, out, err = ''):
295
def communicate(self):
296
return (self.out, self.err)
300
00000000 g F .text\t00000001 foo
301
00000000 g F .text._Z6foobarv\t00000001 _Z6foobarv
302
00000000 g F .text.hello\t00000001 hello
303
00000000 g F .text._ZThn4_6foobarv\t00000001 _ZThn4_6foobarv
306
00000000 g F .text.hi\t00000001 hi
307
00000000 g F .text.hot._Z6barbazv\t00000001 .hidden _Z6barbazv
312
ld: ICF folding section '.text.hello' in file 'foo.o'into '.text.hi' in file 'bar.o'
313
ld: ICF folding section '.foo' in file 'foo.o'into '.foo' in file 'bar.o'
316
class SubprocessPopen(object):
317
def __init__(self, test):
320
def __call__(self, args, stdout = None, stderr = None):
321
self.test.assertEqual(stdout, subprocess.PIPE)
322
self.test.assertEqual(stderr, subprocess.PIPE)
323
if args[0] == 'objdump':
324
self.test.assertEqual(args[1], '-t')
325
self.test.assertTrue(args[2] in OBJDUMPS)
326
return FakeProcess(OBJDUMPS[args[2]])
328
return FakeProcess('', PRINT_ICF)
330
class TestSectionFinder(unittest.TestCase):
331
def test_getSections(self):
332
'''Test SectionFinder'''
333
# Divert subprocess.Popen
334
subprocess_popen = subprocess.Popen
335
subprocess.Popen = SubprocessPopen(self)
336
config.EXPAND_LIBS_ORDER_STYLE = 'linkerscript'
337
config.OBJ_SUFFIX = '.o'
338
config.LIB_SUFFIX = '.a'
339
finder = SectionFinder(['foo.o', 'bar.o'])
340
self.assertEqual(finder.getSections('foobar'), [])
341
self.assertEqual(finder.getSections('_Z6barbazv'), ['.text.hot._Z6barbazv'])
342
self.assertEqual(finder.getSections('_Z6foobarv'), ['.text._Z6foobarv', '.text._ZThn4_6foobarv'])
343
self.assertEqual(finder.getSections('_ZThn4_6foobarv'), ['.text._Z6foobarv', '.text._ZThn4_6foobarv'])
344
subprocess.Popen = subprocess_popen
346
class TestSymbolOrder(unittest.TestCase):
347
def test_getOrderedSections(self):
348
'''Test ExpandMoreArgs' _getOrderedSections'''
349
# Divert subprocess.Popen
350
subprocess_popen = subprocess.Popen
351
subprocess.Popen = SubprocessPopen(self)
352
config.EXPAND_LIBS_ORDER_STYLE = 'linkerscript'
353
config.OBJ_SUFFIX = '.o'
354
config.LIB_SUFFIX = '.a'
355
config.LD_PRINT_ICF_SECTIONS = ''
356
args = ExpandArgsMore(['foo', '-bar', 'bar.o', 'foo.o'])
357
self.assertEqual(args._getOrderedSections(['_Z6foobarv', '_Z6barbazv']), ['.text._Z6foobarv', '.text._ZThn4_6foobarv', '.text.hot._Z6barbazv'])
358
self.assertEqual(args._getOrderedSections(['_ZThn4_6foobarv', '_Z6barbazv']), ['.text._Z6foobarv', '.text._ZThn4_6foobarv', '.text.hot._Z6barbazv'])
359
subprocess.Popen = subprocess_popen
361
def test_getFoldedSections(self):
362
'''Test ExpandMoreArgs' _getFoldedSections'''
363
# Divert subprocess.Popen
364
subprocess_popen = subprocess.Popen
365
subprocess.Popen = SubprocessPopen(self)
366
config.LD_PRINT_ICF_SECTIONS = '-Wl,--print-icf-sections'
367
args = ExpandArgsMore(['foo', '-bar', 'bar.o', 'foo.o'])
368
self.assertEqual(args._getFoldedSections(), {'.text.hello': '.text.hi', '.text.hi': ['.text.hello']})
369
subprocess.Popen = subprocess_popen
371
def test_getOrderedSectionsWithICF(self):
372
'''Test ExpandMoreArgs' _getOrderedSections, with ICF'''
373
# Divert subprocess.Popen
374
subprocess_popen = subprocess.Popen
375
subprocess.Popen = SubprocessPopen(self)
376
config.EXPAND_LIBS_ORDER_STYLE = 'linkerscript'
377
config.OBJ_SUFFIX = '.o'
378
config.LIB_SUFFIX = '.a'
379
config.LD_PRINT_ICF_SECTIONS = '-Wl,--print-icf-sections'
380
args = ExpandArgsMore(['foo', '-bar', 'bar.o', 'foo.o'])
381
self.assertEqual(args._getOrderedSections(['hello', '_Z6barbazv']), ['.text.hi', '.text.hello', '.text.hot._Z6barbazv'])
382
self.assertEqual(args._getOrderedSections(['_ZThn4_6foobarv', 'hi', '_Z6barbazv']), ['.text._Z6foobarv', '.text._ZThn4_6foobarv', '.text.hi', '.text.hello', '.text.hot._Z6barbazv'])
383
subprocess.Popen = subprocess_popen
386
if __name__ == '__main__':