|
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 |
|
9 |
|
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 } |
|
36 |
|
37 config = sys.modules['expandlibs_config'] = imp.new_module('expandlibs_config') |
|
38 |
|
39 from expandlibs import LibDescriptor, ExpandArgs, relativize, ExpandLibsDeps |
|
40 from expandlibs_gen import generate |
|
41 from expandlibs_exec import ExpandArgsMore, SectionFinder |
|
42 |
|
43 def Lib(name): |
|
44 return config.LIB_PREFIX + name + config.LIB_SUFFIX |
|
45 |
|
46 def Obj(name): |
|
47 return name + config.OBJ_SUFFIX |
|
48 |
|
49 def Dll(name): |
|
50 return config.DLL_PREFIX + name + config.DLL_SUFFIX |
|
51 |
|
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 |
|
55 |
|
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 |
|
70 |
|
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])) |
|
85 |
|
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) |
|
97 |
|
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 |
|
111 |
|
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) |
|
122 |
|
123 class TestCaseWithTmpDir(unittest.TestCase): |
|
124 __metaclass__ = ReplicateTests |
|
125 def init(self): |
|
126 self.tmpdir = os.path.abspath(mkdtemp(dir=os.curdir)) |
|
127 |
|
128 def cleanup(self): |
|
129 rmtree(self.tmpdir) |
|
130 |
|
131 def touch(self, files): |
|
132 for f in files: |
|
133 open(f, 'w').close() |
|
134 |
|
135 def tmpfile(self, *args): |
|
136 return os.path.join(self.tmpdir, *args) |
|
137 |
|
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]) |
|
145 |
|
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']]) |
|
149 |
|
150 self.assertRaises(Exception, generate, files + [self.tmpfile(Obj('z'))]) |
|
151 self.assertRaises(Exception, generate, files + [self.tmpfile(Lib('y'))]) |
|
152 |
|
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'))]))) |
|
168 |
|
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) |
|
176 |
|
177 def assertRelEqual(self, args1, args2): |
|
178 self.assertEqual(args1, [relativize(a) for a in args2]) |
|
179 |
|
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) |
|
188 |
|
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'))]) |
|
194 |
|
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'))]) |
|
198 |
|
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)]) |
|
206 |
|
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)]) |
|
212 |
|
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)) |
|
216 |
|
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) |
|
223 |
|
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'))]) |
|
229 |
|
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) |
|
244 |
|
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])) |
|
248 |
|
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 |
|
282 |
|
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 |
|
294 |
|
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'))]) |
|
299 |
|
300 # ExpandArgsMore also has an extra method extracting static libraries |
|
301 # when possible |
|
302 args.extract() |
|
303 |
|
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) |
|
315 |
|
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])) |
|
319 |
|
320 # Restore subprocess.call |
|
321 subprocess.call = subprocess_call |
|
322 |
|
323 class FakeProcess(object): |
|
324 def __init__(self, out, err = ''): |
|
325 self.out = out |
|
326 self.err = err |
|
327 |
|
328 def communicate(self): |
|
329 return (self.out, self.err) |
|
330 |
|
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 } |
|
343 |
|
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 ''' |
|
348 |
|
349 class SubprocessPopen(object): |
|
350 def __init__(self, test): |
|
351 self.test = test |
|
352 |
|
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) |
|
362 |
|
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 |
|
378 |
|
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 |
|
393 |
|
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 |
|
403 |
|
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 |
|
417 |
|
418 |
|
419 if __name__ == '__main__': |
|
420 mozunit.main() |