6 from tempfile
import mkdtemp
7 from shutil
import rmtree
10 from UserString
import UserString
11 # Create a controlled configuration for use by expandlibs
20 'IMPORT_LIB_SUFFIX': '.lib',
21 'LIBS_DESC_SUFFIX': '.desc',
22 'EXPAND_LIBS_LIST_STYLE': 'list',
26 'AR_EXTRACT': 'ar -x',
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
44 return config
.LIB_PREFIX
+ name
+ config
.LIB_SUFFIX
47 return name
+ config
.OBJ_SUFFIX
50 return config
.DLL_PREFIX
+ name
+ config
.DLL_SUFFIX
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
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]))
87 '''Test LibDescriptor's initialization'''
88 desc_list
= ["# Comment",
89 "{0} = a b".format(LibDescriptor
.KEYS
[1]),
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'''
102 setattr(config
, key
, conf
[key
])
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)'
121 return type.__new
__(cls
, clsName
, bases
, dict)
123 class TestCaseWithTmpDir(unittest
.TestCase
):
124 __metaclass__
= ReplicateTests
126 self
.tmpdir
= os
.path
.abspath(mkdtemp(dir=os
.curdir
))
131 def touch(self
, files
):
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
):
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
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
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], '@')
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:]
224 with
open(filename
, 'r') as f
:
225 self
.assertRelEqual([l
.strip() for l
in f
.readlines() if len(l
.strip())], content
)
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
)
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)
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
:
261 extracted
[lib
].extend(extract
)
263 subprocess
.call
= call
265 def check_output(args
, **kargs
):
266 # The command called is always 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:]],
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'))])
284 # ExpandArgsMore also has an extra method extracting static libraries
288 files
= self
.files
+ self
.liby_files
+ self
.libx_files
289 # With AR_EXTRACT, it uses the descriptors when there are, and
291 # extracts the remaining libraries
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.
305 extracted_args
.extend(sorted(extracted
[base
]))
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'))])
312 extracted_args
.append(f
)
313 self
.assertRelEqual(args
, ['foo', '-bar'] + extracted_args
)
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'))
323 os
.makedirs(self
.tmpfile('foo', 'bar'))
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
= ''):
338 def communicate(self
):
339 return (self
.out
, self
.err
)
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
349 00000000 g F .text.hi\t00000001 hi
350 00000000 g F .text.hot._Z6barbazv\t00000001 .hidden _Z6barbazv
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
):
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]])
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__':