Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / native_client_sdk / src / tools / tests / create_nmf_test.py
blob03b802a3147de6f37105fcf47f4cc9b31a35fc7b
1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5 import json
6 import os
7 import posixpath
8 import shutil
9 import subprocess
10 import sys
11 import tempfile
12 import unittest
14 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
15 TOOLS_DIR = os.path.dirname(SCRIPT_DIR)
16 DATA_DIR = os.path.join(TOOLS_DIR, 'lib', 'tests', 'data')
17 BUILD_TOOLS_DIR = os.path.join(os.path.dirname(TOOLS_DIR), 'build_tools')
18 CHROME_SRC = os.path.dirname(os.path.dirname(os.path.dirname(TOOLS_DIR)))
19 MOCK_DIR = os.path.join(CHROME_SRC, 'third_party', 'pymock')
21 # For the mock library
22 sys.path.append(MOCK_DIR)
23 sys.path.append(TOOLS_DIR)
24 sys.path.append(BUILD_TOOLS_DIR)
26 import build_paths
27 import create_nmf
28 import getos
29 from mock import patch, Mock
31 TOOLCHAIN_OUT = os.path.join(build_paths.OUT_DIR, 'sdk_tests', 'toolchain')
32 NACL_X86_GLIBC_TOOLCHAIN = os.path.join(TOOLCHAIN_OUT,
33 '%s_x86' % getos.GetPlatform(),
34 'nacl_x86_glibc')
36 PosixRelPath = create_nmf.PosixRelPath
39 def StripSo(name):
40 """Strip trailing hexidecimal characters from the name of a shared object.
42 It strips everything after the last '.' in the name, and checks that the new
43 name ends with .so.
45 e.g.
47 libc.so.ad6acbfa => libc.so
48 foo.bar.baz => foo.bar.baz
49 """
50 stripped_name = '.'.join(name.split('.')[:-1])
51 if stripped_name.endswith('.so'):
52 return stripped_name
53 return name
56 class TestPosixRelPath(unittest.TestCase):
57 def testBasic(self):
58 # Note that PosixRelPath only converts from native path format to posix
59 # path format, that's why we have to use os.path.join here.
60 path = os.path.join(os.path.sep, 'foo', 'bar', 'baz.blah')
61 start = os.path.sep + 'foo'
62 self.assertEqual(PosixRelPath(path, start), 'bar/baz.blah')
65 class TestDefaultLibpath(unittest.TestCase):
66 def setUp(self):
67 patcher = patch('create_nmf.GetSDKRoot', Mock(return_value='/dummy/path'))
68 patcher.start()
69 self.addCleanup(patcher.stop)
71 def testUsesSDKRoot(self):
72 paths = create_nmf.GetDefaultLibPath('Debug')
73 for path in paths:
74 self.assertTrue(path.startswith('/dummy/path'))
76 def testFallbackPath(self):
77 paths = create_nmf.GetDefaultLibPath('foo_Debug')
78 if sys.platform == 'win32':
79 paths = [p.replace('\\', '/') for p in paths]
80 path_base = '/dummy/path/lib/glibc_x86_64/foo_Debug'
81 path_fallback = '/dummy/path/lib/glibc_x86_64/Debug'
82 self.assertIn(path_base, paths)
83 self.assertIn(path_fallback, paths)
84 self.assertGreater(paths.index(path_fallback), paths.index(path_base))
86 paths = create_nmf.GetDefaultLibPath('foo_bar')
87 if sys.platform == 'win32':
88 paths = [p.replace('\\', '/') for p in paths]
89 path_base = '/dummy/path/lib/glibc_x86_64/foo_bar'
90 path_fallback = '/dummy/path/lib/glibc_x86_64/Release'
91 self.assertIn(path_base, paths)
92 self.assertIn(path_fallback, paths)
93 self.assertGreater(paths.index(path_fallback), paths.index(path_base))
95 def testIncludesNaClPorts(self):
96 paths = create_nmf.GetDefaultLibPath('Debug')
97 self.assertTrue(any(os.path.join('ports', 'lib') in p for p in paths),
98 'naclports libpath missing: %s' % str(paths))
101 class TestNmfUtils(unittest.TestCase):
102 """Tests for the main NmfUtils class in create_nmf."""
104 def setUp(self):
105 self.tempdir = None
106 self.toolchain = NACL_X86_GLIBC_TOOLCHAIN
107 self.objdump = os.path.join(self.toolchain, 'bin', 'i686-nacl-objdump')
108 if os.name == 'nt':
109 self.objdump += '.exe'
110 self._Mktemp()
112 def _CreateTestNexe(self, name, arch):
113 """Create an empty test .nexe file for use in create_nmf tests.
115 This is used rather than checking in test binaries since the
116 checked in binaries depend on .so files that only exist in the
117 certain SDK that build them.
119 compiler = os.path.join(self.toolchain, 'bin', '%s-nacl-g++' % arch)
120 if os.name == 'nt':
121 compiler += '.exe'
122 os.environ['CYGWIN'] = 'nodosfilewarning'
123 program = 'int main() { return 0; }'
124 name = os.path.join(self.tempdir, name)
125 dst_dir = os.path.dirname(name)
126 if not os.path.exists(dst_dir):
127 os.makedirs(dst_dir)
128 self.assertTrue(os.path.exists(compiler), 'compiler missing: %s' % compiler)
129 cmd = [compiler, '-pthread', '-x' , 'c', '-o', name, '-']
130 p = subprocess.Popen(cmd, stdin=subprocess.PIPE)
131 p.communicate(input=program)
132 self.assertEqual(p.returncode, 0)
133 return name
135 def tearDown(self):
136 if self.tempdir:
137 shutil.rmtree(self.tempdir)
139 def _Mktemp(self):
140 self.tempdir = tempfile.mkdtemp()
142 def _CreateNmfUtils(self, nexes, **kwargs):
143 if not kwargs.get('lib_path'):
144 # Use lib instead of lib64 (lib64 is a symlink to lib).
145 kwargs['lib_path'] = [
146 os.path.join(self.toolchain, 'x86_64-nacl', 'lib'),
147 os.path.join(self.toolchain, 'x86_64-nacl', 'lib32')]
148 return create_nmf.NmfUtils(nexes,
149 objdump=self.objdump,
150 **kwargs)
152 def _CreateStatic(self, arch_path=None, **kwargs):
153 """Copy all static .nexe files from the DATA_DIR to a temporary directory.
155 Args:
156 arch_path: A dictionary mapping architecture to the directory to generate
157 the .nexe for the architecture in.
158 kwargs: Keyword arguments to pass through to create_nmf.NmfUtils
159 constructor.
161 Returns:
162 A tuple with 2 elements:
163 * The generated NMF as a dictionary (i.e. parsed by json.loads)
164 * A list of the generated .nexe paths
166 arch_path = arch_path or {}
167 nexes = []
168 for arch in ('x86_64', 'x86_32', 'arm'):
169 nexe_name = 'test_static_%s.nexe' % arch
170 src_nexe = os.path.join(DATA_DIR, nexe_name)
171 dst_nexe = os.path.join(self.tempdir, arch_path.get(arch, ''), nexe_name)
172 dst_dir = os.path.dirname(dst_nexe)
173 if not os.path.exists(dst_dir):
174 os.makedirs(dst_dir)
175 shutil.copy(src_nexe, dst_nexe)
176 nexes.append(dst_nexe)
178 nexes.sort()
179 nmf_utils = self._CreateNmfUtils(nexes, **kwargs)
180 nmf = json.loads(nmf_utils.GetJson())
181 return nmf, nexes
183 def _CreateDynamicAndStageDeps(self, arch_path=None, **kwargs):
184 """Create dynamic .nexe files and put them in a temporary directory, with
185 their dependencies staged in the same directory.
187 Args:
188 arch_path: A dictionary mapping architecture to the directory to generate
189 the .nexe for the architecture in.
190 kwargs: Keyword arguments to pass through to create_nmf.NmfUtils
191 constructor.
193 Returns:
194 A tuple with 2 elements:
195 * The generated NMF as a dictionary (i.e. parsed by json.loads)
196 * A list of the generated .nexe paths
198 arch_path = arch_path or {}
199 nexes = []
200 for arch in ('x86_64', 'x86_32'):
201 nexe_name = 'test_dynamic_%s.nexe' % arch
202 rel_nexe = os.path.join(arch_path.get(arch, ''), nexe_name)
203 arch_alt = 'i686' if arch == 'x86_32' else arch
204 nexe = self._CreateTestNexe(rel_nexe, arch_alt)
205 nexes.append(nexe)
207 nexes.sort()
208 nmf_utils = self._CreateNmfUtils(nexes, **kwargs)
209 nmf = json.loads(nmf_utils.GetJson())
210 nmf_utils.StageDependencies(self.tempdir)
212 return nmf, nexes
214 def _CreatePexe(self, **kwargs):
215 """Copy test.pexe from the DATA_DIR to a temporary directory.
217 Args:
218 kwargs: Keyword arguments to pass through to create_nmf.NmfUtils
219 constructor.
221 Returns:
222 A tuple with 2 elements:
223 * The generated NMF as a dictionary (i.e. parsed by json.loads)
224 * A list of the generated .pexe paths
226 pexe_name = 'test.pexe'
227 src_pexe = os.path.join(DATA_DIR, pexe_name)
228 dst_pexe = os.path.join(self.tempdir, pexe_name)
229 shutil.copy(src_pexe, dst_pexe)
231 pexes = [dst_pexe]
232 nmf_utils = self._CreateNmfUtils(pexes, **kwargs)
233 nmf = json.loads(nmf_utils.GetJson())
235 return nmf, pexes
237 def _CreateBitCode(self, **kwargs):
238 """Copy test.bc from the DATA_DIR to a temporary directory.
240 Args:
241 kwargs: Keyword arguments to pass through to create_nmf.NmfUtils
242 constructor.
244 Returns:
245 A tuple with 2 elements:
246 * The generated NMF as a dictionary (i.e. parsed by json.loads)
247 * A list of the generated .bc paths
249 bc_name = 'test.bc'
250 src_bc = os.path.join(DATA_DIR, bc_name)
251 dst_bc = os.path.join(self.tempdir, bc_name)
252 shutil.copy(src_bc, dst_bc)
254 bcs = [dst_bc]
255 nmf_utils = self._CreateNmfUtils(bcs, **kwargs)
256 nmf = json.loads(nmf_utils.GetJson())
258 return nmf, bcs
260 def assertManifestEquals(self, manifest, expected):
261 """Compare two manifest dictionaries.
263 The input manifest is regenerated with all string keys and values being
264 processed through StripSo, to remove the random hexidecimal characters at
265 the end of shared object names.
267 Args:
268 manifest: The generated manifest.
269 expected: The expected manifest.
271 def StripSoCopyDict(d):
272 new_d = {}
273 for k, v in d.iteritems():
274 new_k = StripSo(k)
275 if isinstance(v, (str, unicode)):
276 new_v = StripSo(v)
277 elif type(v) is list:
278 new_v = v[:]
279 elif type(v) is dict:
280 new_v = StripSoCopyDict(v)
281 else:
282 # Assume that anything else can be copied directly.
283 new_v = v
285 new_d[new_k] = new_v
286 return new_d
288 self.assertEqual(StripSoCopyDict(manifest), expected)
290 def assertStagingEquals(self, expected):
291 """Compare the contents of the temporary directory, to an expected
292 directory layout.
294 Args:
295 expected: The expected directory layout.
297 all_files = []
298 for root, _, files in os.walk(self.tempdir):
299 rel_root_posix = PosixRelPath(root, self.tempdir)
300 for f in files:
301 path = posixpath.join(rel_root_posix, StripSo(f))
302 if path.startswith('./'):
303 path = path[2:]
304 all_files.append(path)
305 self.assertEqual(set(expected), set(all_files))
308 def testStatic(self):
309 nmf, _ = self._CreateStatic()
310 expected_manifest = {
311 'files': {},
312 'program': {
313 'x86-64': {'url': 'test_static_x86_64.nexe'},
314 'x86-32': {'url': 'test_static_x86_32.nexe'},
315 'arm': {'url': 'test_static_arm.nexe'},
318 self.assertManifestEquals(nmf, expected_manifest)
320 def testStaticWithPath(self):
321 arch_dir = {'x86_32': 'x86_32', 'x86_64': 'x86_64', 'arm': 'arm'}
322 nmf, _ = self._CreateStatic(arch_dir, nmf_root=self.tempdir)
323 expected_manifest = {
324 'files': {},
325 'program': {
326 'x86-32': {'url': 'x86_32/test_static_x86_32.nexe'},
327 'x86-64': {'url': 'x86_64/test_static_x86_64.nexe'},
328 'arm': {'url': 'arm/test_static_arm.nexe'},
331 self.assertManifestEquals(nmf, expected_manifest)
333 def testStaticWithPathNoNmfRoot(self):
334 # This case is not particularly useful, but it is similar to how create_nmf
335 # used to work. If there is no nmf_root given, all paths are relative to
336 # the first nexe passed on the commandline. I believe the assumption
337 # previously was that all .nexes would be in the same directory.
338 arch_dir = {'x86_32': 'x86_32', 'x86_64': 'x86_64', 'arm': 'arm'}
339 nmf, _ = self._CreateStatic(arch_dir)
340 expected_manifest = {
341 'files': {},
342 'program': {
343 'x86-32': {'url': '../x86_32/test_static_x86_32.nexe'},
344 'x86-64': {'url': '../x86_64/test_static_x86_64.nexe'},
345 'arm': {'url': 'test_static_arm.nexe'},
348 self.assertManifestEquals(nmf, expected_manifest)
350 def testStaticWithNexePrefix(self):
351 nmf, _ = self._CreateStatic(nexe_prefix='foo')
352 expected_manifest = {
353 'files': {},
354 'program': {
355 'x86-64': {'url': 'foo/test_static_x86_64.nexe'},
356 'x86-32': {'url': 'foo/test_static_x86_32.nexe'},
357 'arm': {'url': 'foo/test_static_arm.nexe'},
360 self.assertManifestEquals(nmf, expected_manifest)
362 def testDynamic(self):
363 nmf, nexes = self._CreateDynamicAndStageDeps()
364 expected_manifest = {
365 'files': {
366 'main.nexe': {
367 'x86-32': {'url': 'test_dynamic_x86_32.nexe'},
368 'x86-64': {'url': 'test_dynamic_x86_64.nexe'},
370 'libc.so': {
371 'x86-32': {'url': 'lib32/libc.so'},
372 'x86-64': {'url': 'lib64/libc.so'},
374 'libgcc_s.so': {
375 'x86-32': {'url': 'lib32/libgcc_s.so'},
376 'x86-64': {'url': 'lib64/libgcc_s.so'},
378 'libpthread.so': {
379 'x86-32': {'url': 'lib32/libpthread.so'},
380 'x86-64': {'url': 'lib64/libpthread.so'},
383 'program': {
384 'x86-32': {'url': 'lib32/runnable-ld.so'},
385 'x86-64': {'url': 'lib64/runnable-ld.so'},
389 expected_staging = [os.path.basename(f) for f in nexes]
390 expected_staging.extend([
391 'lib32/libc.so',
392 'lib32/libgcc_s.so',
393 'lib32/libpthread.so',
394 'lib32/runnable-ld.so',
395 'lib64/libc.so',
396 'lib64/libgcc_s.so',
397 'lib64/libpthread.so',
398 'lib64/runnable-ld.so'])
400 self.assertManifestEquals(nmf, expected_manifest)
401 self.assertStagingEquals(expected_staging)
403 def testDynamicWithPath(self):
404 arch_dir = {'x86_64': 'x86_64', 'x86_32': 'x86_32'}
405 nmf, nexes = self._CreateDynamicAndStageDeps(arch_dir,
406 nmf_root=self.tempdir)
407 expected_manifest = {
408 'files': {
409 'main.nexe': {
410 'x86-32': {'url': 'x86_32/test_dynamic_x86_32.nexe'},
411 'x86-64': {'url': 'x86_64/test_dynamic_x86_64.nexe'},
413 'libc.so': {
414 'x86-32': {'url': 'x86_32/lib32/libc.so'},
415 'x86-64': {'url': 'x86_64/lib64/libc.so'},
417 'libgcc_s.so': {
418 'x86-32': {'url': 'x86_32/lib32/libgcc_s.so'},
419 'x86-64': {'url': 'x86_64/lib64/libgcc_s.so'},
421 'libpthread.so': {
422 'x86-32': {'url': 'x86_32/lib32/libpthread.so'},
423 'x86-64': {'url': 'x86_64/lib64/libpthread.so'},
426 'program': {
427 'x86-32': {'url': 'x86_32/lib32/runnable-ld.so'},
428 'x86-64': {'url': 'x86_64/lib64/runnable-ld.so'},
432 expected_staging = [PosixRelPath(f, self.tempdir) for f in nexes]
433 expected_staging.extend([
434 'x86_32/lib32/libc.so',
435 'x86_32/lib32/libgcc_s.so',
436 'x86_32/lib32/libpthread.so',
437 'x86_32/lib32/runnable-ld.so',
438 'x86_64/lib64/libc.so',
439 'x86_64/lib64/libgcc_s.so',
440 'x86_64/lib64/libpthread.so',
441 'x86_64/lib64/runnable-ld.so'])
443 self.assertManifestEquals(nmf, expected_manifest)
444 self.assertStagingEquals(expected_staging)
446 def testDynamicWithRelPath(self):
447 """Test that when the nmf root is a relative path that things work."""
448 arch_dir = {'x86_64': 'x86_64', 'x86_32': 'x86_32'}
449 old_path = os.getcwd()
450 try:
451 os.chdir(self.tempdir)
452 nmf, nexes = self._CreateDynamicAndStageDeps(arch_dir, nmf_root='')
453 expected_manifest = {
454 'files': {
455 'main.nexe': {
456 'x86-32': {'url': 'x86_32/test_dynamic_x86_32.nexe'},
457 'x86-64': {'url': 'x86_64/test_dynamic_x86_64.nexe'},
459 'libc.so': {
460 'x86-32': {'url': 'x86_32/lib32/libc.so'},
461 'x86-64': {'url': 'x86_64/lib64/libc.so'},
463 'libgcc_s.so': {
464 'x86-32': {'url': 'x86_32/lib32/libgcc_s.so'},
465 'x86-64': {'url': 'x86_64/lib64/libgcc_s.so'},
467 'libpthread.so': {
468 'x86-32': {'url': 'x86_32/lib32/libpthread.so'},
469 'x86-64': {'url': 'x86_64/lib64/libpthread.so'},
472 'program': {
473 'x86-32': {'url': 'x86_32/lib32/runnable-ld.so'},
474 'x86-64': {'url': 'x86_64/lib64/runnable-ld.so'},
478 expected_staging = [PosixRelPath(f, self.tempdir) for f in nexes]
479 expected_staging.extend([
480 'x86_32/lib32/libc.so',
481 'x86_32/lib32/libgcc_s.so',
482 'x86_32/lib32/libpthread.so',
483 'x86_32/lib32/runnable-ld.so',
484 'x86_64/lib64/libc.so',
485 'x86_64/lib64/libgcc_s.so',
486 'x86_64/lib64/libpthread.so',
487 'x86_64/lib64/runnable-ld.so'])
489 self.assertManifestEquals(nmf, expected_manifest)
490 self.assertStagingEquals(expected_staging)
491 finally:
492 os.chdir(old_path)
494 def testDynamicWithPathNoArchPrefix(self):
495 arch_dir = {'x86_64': 'x86_64', 'x86_32': 'x86_32'}
496 nmf, nexes = self._CreateDynamicAndStageDeps(arch_dir,
497 nmf_root=self.tempdir,
498 no_arch_prefix=True)
499 expected_manifest = {
500 'files': {
501 'main.nexe': {
502 'x86-32': {'url': 'x86_32/test_dynamic_x86_32.nexe'},
503 'x86-64': {'url': 'x86_64/test_dynamic_x86_64.nexe'},
505 'libc.so': {
506 'x86-32': {'url': 'x86_32/libc.so'},
507 'x86-64': {'url': 'x86_64/libc.so'},
509 'libgcc_s.so': {
510 'x86-32': {'url': 'x86_32/libgcc_s.so'},
511 'x86-64': {'url': 'x86_64/libgcc_s.so'},
513 'libpthread.so': {
514 'x86-32': {'url': 'x86_32/libpthread.so'},
515 'x86-64': {'url': 'x86_64/libpthread.so'},
518 'program': {
519 'x86-32': {'url': 'x86_32/runnable-ld.so'},
520 'x86-64': {'url': 'x86_64/runnable-ld.so'},
524 expected_staging = [PosixRelPath(f, self.tempdir) for f in nexes]
525 expected_staging.extend([
526 'x86_32/libc.so',
527 'x86_32/libgcc_s.so',
528 'x86_32/libpthread.so',
529 'x86_32/runnable-ld.so',
530 'x86_64/libc.so',
531 'x86_64/libgcc_s.so',
532 'x86_64/libpthread.so',
533 'x86_64/runnable-ld.so'])
535 self.assertManifestEquals(nmf, expected_manifest)
536 self.assertStagingEquals(expected_staging)
538 def testDynamicWithLibPrefix(self):
539 nmf, nexes = self._CreateDynamicAndStageDeps(lib_prefix='foo')
540 expected_manifest = {
541 'files': {
542 'main.nexe': {
543 'x86-32': {'url': 'test_dynamic_x86_32.nexe'},
544 'x86-64': {'url': 'test_dynamic_x86_64.nexe'},
546 'libc.so': {
547 'x86-32': {'url': 'foo/lib32/libc.so'},
548 'x86-64': {'url': 'foo/lib64/libc.so'},
550 'libgcc_s.so': {
551 'x86-32': {'url': 'foo/lib32/libgcc_s.so'},
552 'x86-64': {'url': 'foo/lib64/libgcc_s.so'},
554 'libpthread.so': {
555 'x86-32': {'url': 'foo/lib32/libpthread.so'},
556 'x86-64': {'url': 'foo/lib64/libpthread.so'},
559 'program': {
560 'x86-32': {'url': 'foo/lib32/runnable-ld.so'},
561 'x86-64': {'url': 'foo/lib64/runnable-ld.so'},
565 expected_staging = [PosixRelPath(f, self.tempdir) for f in nexes]
566 expected_staging.extend([
567 'foo/lib32/libc.so',
568 'foo/lib32/libgcc_s.so',
569 'foo/lib32/libpthread.so',
570 'foo/lib32/runnable-ld.so',
571 'foo/lib64/libc.so',
572 'foo/lib64/libgcc_s.so',
573 'foo/lib64/libpthread.so',
574 'foo/lib64/runnable-ld.so'])
576 self.assertManifestEquals(nmf, expected_manifest)
577 self.assertStagingEquals(expected_staging)
579 def testPexe(self):
580 nmf, _ = self._CreatePexe()
581 expected_manifest = {
582 'program': {
583 'portable': {
584 'pnacl-translate': {
585 'url': 'test.pexe'
590 self.assertManifestEquals(nmf, expected_manifest)
592 def testPexeOptLevel(self):
593 nmf, _ = self._CreatePexe(pnacl_optlevel=2)
594 expected_manifest = {
595 'program': {
596 'portable': {
597 'pnacl-translate': {
598 'url': 'test.pexe',
599 'optlevel': 2,
604 self.assertManifestEquals(nmf, expected_manifest)
606 def testBitCode(self):
607 nmf, _ = self._CreateBitCode(pnacl_debug_optlevel=0)
608 expected_manifest = {
609 'program': {
610 'portable': {
611 'pnacl-debug': {
612 'url': 'test.bc',
613 'optlevel': 0,
618 self.assertManifestEquals(nmf, expected_manifest)
621 if __name__ == '__main__':
622 unittest.main()