create_nmf_test.py revision c5cede9ae108bb15f6b7a8aea21c7e1fefa2834c
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. 5import json 6import os 7import posixpath 8import shutil 9import subprocess 10import sys 11import tempfile 12import unittest 13 14SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) 15TOOLS_DIR = os.path.dirname(SCRIPT_DIR) 16DATA_DIR = os.path.join(TOOLS_DIR, 'lib', 'tests', 'data') 17CHROME_SRC = os.path.dirname(os.path.dirname(os.path.dirname(TOOLS_DIR))) 18MOCK_DIR = os.path.join(CHROME_SRC, "third_party", "pymock") 19 20# For the mock library 21sys.path.append(MOCK_DIR) 22sys.path.append(TOOLS_DIR) 23 24import create_nmf 25import getos 26import mock 27 28 29PosixRelPath = create_nmf.PosixRelPath 30 31 32def StripSo(name): 33 """Strip trailing hexidecimal characters from the name of a shared object. 34 35 It strips everything after the last '.' in the name, and checks that the new 36 name ends with .so. 37 38 e.g. 39 40 libc.so.ad6acbfa => libc.so 41 foo.bar.baz => foo.bar.baz 42 """ 43 stripped_name = '.'.join(name.split('.')[:-1]) 44 if stripped_name.endswith('.so'): 45 return stripped_name 46 return name 47 48 49class TestPosixRelPath(unittest.TestCase): 50 def testBasic(self): 51 # Note that PosixRelPath only converts from native path format to posix 52 # path format, that's why we have to use os.path.join here. 53 path = os.path.join(os.path.sep, 'foo', 'bar', 'baz.blah') 54 start = os.path.sep + 'foo' 55 self.assertEqual(PosixRelPath(path, start), 'bar/baz.blah') 56 57 58class TestDefaultLibpath(unittest.TestCase): 59 def testWithoutNaClSDKRoot(self): 60 """GetDefaultLibPath wihtout NACL_SDK_ROOT set 61 62 In the absence of NACL_SDK_ROOT GetDefaultLibPath should 63 return the empty list.""" 64 with mock.patch.dict('os.environ', clear=True): 65 paths = create_nmf.GetDefaultLibPath('Debug') 66 self.assertEqual(paths, []) 67 68 def testHonorNaClSDKRoot(self): 69 with mock.patch.dict('os.environ', {'NACL_SDK_ROOT': '/dummy/path'}): 70 paths = create_nmf.GetDefaultLibPath('Debug') 71 for path in paths: 72 self.assertTrue(path.startswith('/dummy/path')) 73 74 def testIncludesNaClPorts(self): 75 with mock.patch.dict('os.environ', {'NACL_SDK_ROOT': '/dummy/path'}): 76 paths = create_nmf.GetDefaultLibPath('Debug') 77 self.assertTrue(any(os.path.join('ports', 'lib') in p for p in paths), 78 "naclports libpath missing: %s" % str(paths)) 79 80 81class TestNmfUtils(unittest.TestCase): 82 """Tests for the main NmfUtils class in create_nmf.""" 83 84 def setUp(self): 85 self.tempdir = None 86 toolchain = os.path.join(CHROME_SRC, 'native_client', 'toolchain') 87 self.toolchain = os.path.join(toolchain, '%s_x86' % getos.GetPlatform(), 88 'nacl_x86_glibc') 89 self.objdump = os.path.join(self.toolchain, 'bin', 'i686-nacl-objdump') 90 if os.name == 'nt': 91 self.objdump += '.exe' 92 self._Mktemp() 93 94 def _CreateTestNexe(self, name, arch): 95 """Create an empty test .nexe file for use in create_nmf tests. 96 97 This is used rather than checking in test binaries since the 98 checked in binaries depend on .so files that only exist in the 99 certain SDK that build them. 100 """ 101 compiler = os.path.join(self.toolchain, 'bin', '%s-nacl-g++' % arch) 102 if os.name == 'nt': 103 compiler += '.exe' 104 os.environ['CYGWIN'] = 'nodosfilewarning' 105 program = 'int main() { return 0; }' 106 name = os.path.join(self.tempdir, name) 107 dst_dir = os.path.dirname(name) 108 if not os.path.exists(dst_dir): 109 os.makedirs(dst_dir) 110 cmd = [compiler, '-pthread', '-x' , 'c', '-o', name, '-'] 111 p = subprocess.Popen(cmd, stdin=subprocess.PIPE) 112 p.communicate(input=program) 113 self.assertEqual(p.returncode, 0) 114 return name 115 116 def tearDown(self): 117 if self.tempdir: 118 shutil.rmtree(self.tempdir) 119 120 def _Mktemp(self): 121 self.tempdir = tempfile.mkdtemp() 122 123 def _CreateNmfUtils(self, nexes, **kwargs): 124 if not kwargs.get('lib_path'): 125 # Use lib instead of lib64 (lib64 is a symlink to lib). 126 kwargs['lib_path'] = [ 127 os.path.join(self.toolchain, 'x86_64-nacl', 'lib'), 128 os.path.join(self.toolchain, 'x86_64-nacl', 'lib32')] 129 return create_nmf.NmfUtils(nexes, 130 objdump=self.objdump, 131 **kwargs) 132 133 def _CreateStatic(self, arch_path=None, **kwargs): 134 """Copy all static .nexe files from the DATA_DIR to a temporary directory. 135 136 Args: 137 arch_path: A dictionary mapping architecture to the directory to generate 138 the .nexe for the architecture in. 139 kwargs: Keyword arguments to pass through to create_nmf.NmfUtils 140 constructor. 141 142 Returns: 143 A tuple with 2 elements: 144 * The generated NMF as a dictionary (i.e. parsed by json.loads) 145 * A list of the generated .nexe paths 146 """ 147 arch_path = arch_path or {} 148 nexes = [] 149 for arch in ('x86_64', 'x86_32', 'arm'): 150 nexe_name = 'test_static_%s.nexe' % arch 151 src_nexe = os.path.join(DATA_DIR, nexe_name) 152 dst_nexe = os.path.join(self.tempdir, arch_path.get(arch, ''), nexe_name) 153 dst_dir = os.path.dirname(dst_nexe) 154 if not os.path.exists(dst_dir): 155 os.makedirs(dst_dir) 156 shutil.copy(src_nexe, dst_nexe) 157 nexes.append(dst_nexe) 158 159 nexes.sort() 160 nmf_utils = self._CreateNmfUtils(nexes, **kwargs) 161 nmf = json.loads(nmf_utils.GetJson()) 162 return nmf, nexes 163 164 def _CreateDynamicAndStageDeps(self, arch_path=None, **kwargs): 165 """Create dynamic .nexe files and put them in a temporary directory, with 166 their dependencies staged in the same directory. 167 168 Args: 169 arch_path: A dictionary mapping architecture to the directory to generate 170 the .nexe for the architecture in. 171 kwargs: Keyword arguments to pass through to create_nmf.NmfUtils 172 constructor. 173 174 Returns: 175 A tuple with 2 elements: 176 * The generated NMF as a dictionary (i.e. parsed by json.loads) 177 * A list of the generated .nexe paths 178 """ 179 arch_path = arch_path or {} 180 nexes = [] 181 for arch in ('x86_64', 'x86_32'): 182 nexe_name = 'test_dynamic_%s.nexe' % arch 183 rel_nexe = os.path.join(arch_path.get(arch, ''), nexe_name) 184 arch_alt = 'i686' if arch == 'x86_32' else arch 185 nexe = self._CreateTestNexe(rel_nexe, arch_alt) 186 nexes.append(nexe) 187 188 nexes.sort() 189 nmf_utils = self._CreateNmfUtils(nexes, **kwargs) 190 nmf = json.loads(nmf_utils.GetJson()) 191 nmf_utils.StageDependencies(self.tempdir) 192 193 return nmf, nexes 194 195 def _CreatePexe(self, **kwargs): 196 """Copy test.pexe from the DATA_DIR to a temporary directory. 197 198 Args: 199 kwargs: Keyword arguments to pass through to create_nmf.NmfUtils 200 constructor. 201 202 Returns: 203 A tuple with 2 elements: 204 * The generated NMF as a dictionary (i.e. parsed by json.loads) 205 * A list of the generated .pexe paths 206 """ 207 pexe_name = 'test.pexe' 208 src_pexe = os.path.join(DATA_DIR, pexe_name) 209 dst_pexe = os.path.join(self.tempdir, pexe_name) 210 shutil.copy(src_pexe, dst_pexe) 211 212 pexes = [dst_pexe] 213 nmf_utils = self._CreateNmfUtils(pexes, **kwargs) 214 nmf = json.loads(nmf_utils.GetJson()) 215 216 return nmf, pexes 217 218 def _CreateBitCode(self, **kwargs): 219 """Copy test.bc from the DATA_DIR to a temporary directory. 220 221 Args: 222 kwargs: Keyword arguments to pass through to create_nmf.NmfUtils 223 constructor. 224 225 Returns: 226 A tuple with 2 elements: 227 * The generated NMF as a dictionary (i.e. parsed by json.loads) 228 * A list of the generated .bc paths 229 """ 230 bc_name = 'test.bc' 231 src_bc = os.path.join(DATA_DIR, bc_name) 232 dst_bc = os.path.join(self.tempdir, bc_name) 233 shutil.copy(src_bc, dst_bc) 234 235 bcs = [dst_bc] 236 nmf_utils = self._CreateNmfUtils(bcs, **kwargs) 237 nmf = json.loads(nmf_utils.GetJson()) 238 239 return nmf, bcs 240 241 def assertManifestEquals(self, manifest, expected): 242 """Compare two manifest dictionaries. 243 244 The input manifest is regenerated with all string keys and values being 245 processed through StripSo, to remove the random hexidecimal characters at 246 the end of shared object names. 247 248 Args: 249 manifest: The generated manifest. 250 expected: The expected manifest. 251 """ 252 def StripSoCopyDict(d): 253 new_d = {} 254 for k, v in d.iteritems(): 255 new_k = StripSo(k) 256 if isinstance(v, (str, unicode)): 257 new_v = StripSo(v) 258 elif type(v) is list: 259 new_v = v[:] 260 elif type(v) is dict: 261 new_v = StripSoCopyDict(v) 262 else: 263 # Assume that anything else can be copied directly. 264 new_v = v 265 266 new_d[new_k] = new_v 267 return new_d 268 269 self.assertEqual(StripSoCopyDict(manifest), expected) 270 271 def assertStagingEquals(self, expected): 272 """Compare the contents of the temporary directory, to an expected 273 directory layout. 274 275 Args: 276 expected: The expected directory layout. 277 """ 278 all_files = [] 279 for root, _, files in os.walk(self.tempdir): 280 rel_root_posix = PosixRelPath(root, self.tempdir) 281 for f in files: 282 path = posixpath.join(rel_root_posix, StripSo(f)) 283 if path.startswith('./'): 284 path = path[2:] 285 all_files.append(path) 286 self.assertEqual(set(expected), set(all_files)) 287 288 289 def testStatic(self): 290 nmf, _ = self._CreateStatic() 291 expected_manifest = { 292 'files': {}, 293 'program': { 294 'x86-64': {'url': 'test_static_x86_64.nexe'}, 295 'x86-32': {'url': 'test_static_x86_32.nexe'}, 296 'arm': {'url': 'test_static_arm.nexe'}, 297 } 298 } 299 self.assertManifestEquals(nmf, expected_manifest) 300 301 def testStaticWithPath(self): 302 arch_dir = {'x86_32': 'x86_32', 'x86_64': 'x86_64', 'arm': 'arm'} 303 nmf, _ = self._CreateStatic(arch_dir, nmf_root=self.tempdir) 304 expected_manifest = { 305 'files': {}, 306 'program': { 307 'x86-32': {'url': 'x86_32/test_static_x86_32.nexe'}, 308 'x86-64': {'url': 'x86_64/test_static_x86_64.nexe'}, 309 'arm': {'url': 'arm/test_static_arm.nexe'}, 310 } 311 } 312 self.assertManifestEquals(nmf, expected_manifest) 313 314 def testStaticWithPathNoNmfRoot(self): 315 # This case is not particularly useful, but it is similar to how create_nmf 316 # used to work. If there is no nmf_root given, all paths are relative to 317 # the first nexe passed on the commandline. I believe the assumption 318 # previously was that all .nexes would be in the same directory. 319 arch_dir = {'x86_32': 'x86_32', 'x86_64': 'x86_64', 'arm': 'arm'} 320 nmf, _ = self._CreateStatic(arch_dir) 321 expected_manifest = { 322 'files': {}, 323 'program': { 324 'x86-32': {'url': '../x86_32/test_static_x86_32.nexe'}, 325 'x86-64': {'url': '../x86_64/test_static_x86_64.nexe'}, 326 'arm': {'url': 'test_static_arm.nexe'}, 327 } 328 } 329 self.assertManifestEquals(nmf, expected_manifest) 330 331 def testStaticWithNexePrefix(self): 332 nmf, _ = self._CreateStatic(nexe_prefix='foo') 333 expected_manifest = { 334 'files': {}, 335 'program': { 336 'x86-64': {'url': 'foo/test_static_x86_64.nexe'}, 337 'x86-32': {'url': 'foo/test_static_x86_32.nexe'}, 338 'arm': {'url': 'foo/test_static_arm.nexe'}, 339 } 340 } 341 self.assertManifestEquals(nmf, expected_manifest) 342 343 def testDynamic(self): 344 nmf, nexes = self._CreateDynamicAndStageDeps() 345 expected_manifest = { 346 'files': { 347 'main.nexe': { 348 'x86-32': {'url': 'test_dynamic_x86_32.nexe'}, 349 'x86-64': {'url': 'test_dynamic_x86_64.nexe'}, 350 }, 351 'libc.so': { 352 'x86-32': {'url': 'lib32/libc.so'}, 353 'x86-64': {'url': 'lib64/libc.so'}, 354 }, 355 'libgcc_s.so': { 356 'x86-32': {'url': 'lib32/libgcc_s.so'}, 357 'x86-64': {'url': 'lib64/libgcc_s.so'}, 358 }, 359 'libpthread.so': { 360 'x86-32': {'url': 'lib32/libpthread.so'}, 361 'x86-64': {'url': 'lib64/libpthread.so'}, 362 }, 363 }, 364 'program': { 365 'x86-32': {'url': 'lib32/runnable-ld.so'}, 366 'x86-64': {'url': 'lib64/runnable-ld.so'}, 367 } 368 } 369 370 expected_staging = [os.path.basename(f) for f in nexes] 371 expected_staging.extend([ 372 'lib32/libc.so', 373 'lib32/libgcc_s.so', 374 'lib32/libpthread.so', 375 'lib32/runnable-ld.so', 376 'lib64/libc.so', 377 'lib64/libgcc_s.so', 378 'lib64/libpthread.so', 379 'lib64/runnable-ld.so']) 380 381 self.assertManifestEquals(nmf, expected_manifest) 382 self.assertStagingEquals(expected_staging) 383 384 def testDynamicWithPath(self): 385 arch_dir = {'x86_64': 'x86_64', 'x86_32': 'x86_32'} 386 nmf, nexes = self._CreateDynamicAndStageDeps(arch_dir, 387 nmf_root=self.tempdir) 388 expected_manifest = { 389 'files': { 390 'main.nexe': { 391 'x86-32': {'url': 'x86_32/test_dynamic_x86_32.nexe'}, 392 'x86-64': {'url': 'x86_64/test_dynamic_x86_64.nexe'}, 393 }, 394 'libc.so': { 395 'x86-32': {'url': 'x86_32/lib32/libc.so'}, 396 'x86-64': {'url': 'x86_64/lib64/libc.so'}, 397 }, 398 'libgcc_s.so': { 399 'x86-32': {'url': 'x86_32/lib32/libgcc_s.so'}, 400 'x86-64': {'url': 'x86_64/lib64/libgcc_s.so'}, 401 }, 402 'libpthread.so': { 403 'x86-32': {'url': 'x86_32/lib32/libpthread.so'}, 404 'x86-64': {'url': 'x86_64/lib64/libpthread.so'}, 405 }, 406 }, 407 'program': { 408 'x86-32': {'url': 'x86_32/lib32/runnable-ld.so'}, 409 'x86-64': {'url': 'x86_64/lib64/runnable-ld.so'}, 410 } 411 } 412 413 expected_staging = [PosixRelPath(f, self.tempdir) for f in nexes] 414 expected_staging.extend([ 415 'x86_32/lib32/libc.so', 416 'x86_32/lib32/libgcc_s.so', 417 'x86_32/lib32/libpthread.so', 418 'x86_32/lib32/runnable-ld.so', 419 'x86_64/lib64/libc.so', 420 'x86_64/lib64/libgcc_s.so', 421 'x86_64/lib64/libpthread.so', 422 'x86_64/lib64/runnable-ld.so']) 423 424 self.assertManifestEquals(nmf, expected_manifest) 425 self.assertStagingEquals(expected_staging) 426 427 def testDynamicWithRelPath(self): 428 """Test that when the nmf root is a relative path that things work.""" 429 arch_dir = {'x86_64': 'x86_64', 'x86_32': 'x86_32'} 430 old_path = os.getcwd() 431 try: 432 os.chdir(self.tempdir) 433 nmf, nexes = self._CreateDynamicAndStageDeps(arch_dir, nmf_root='') 434 expected_manifest = { 435 'files': { 436 'main.nexe': { 437 'x86-32': {'url': 'x86_32/test_dynamic_x86_32.nexe'}, 438 'x86-64': {'url': 'x86_64/test_dynamic_x86_64.nexe'}, 439 }, 440 'libc.so': { 441 'x86-32': {'url': 'x86_32/lib32/libc.so'}, 442 'x86-64': {'url': 'x86_64/lib64/libc.so'}, 443 }, 444 'libgcc_s.so': { 445 'x86-32': {'url': 'x86_32/lib32/libgcc_s.so'}, 446 'x86-64': {'url': 'x86_64/lib64/libgcc_s.so'}, 447 }, 448 'libpthread.so': { 449 'x86-32': {'url': 'x86_32/lib32/libpthread.so'}, 450 'x86-64': {'url': 'x86_64/lib64/libpthread.so'}, 451 }, 452 }, 453 'program': { 454 'x86-32': {'url': 'x86_32/lib32/runnable-ld.so'}, 455 'x86-64': {'url': 'x86_64/lib64/runnable-ld.so'}, 456 } 457 } 458 459 expected_staging = [PosixRelPath(f, self.tempdir) for f in nexes] 460 expected_staging.extend([ 461 'x86_32/lib32/libc.so', 462 'x86_32/lib32/libgcc_s.so', 463 'x86_32/lib32/libpthread.so', 464 'x86_32/lib32/runnable-ld.so', 465 'x86_64/lib64/libc.so', 466 'x86_64/lib64/libgcc_s.so', 467 'x86_64/lib64/libpthread.so', 468 'x86_64/lib64/runnable-ld.so']) 469 470 self.assertManifestEquals(nmf, expected_manifest) 471 self.assertStagingEquals(expected_staging) 472 finally: 473 os.chdir(old_path) 474 475 def testDynamicWithPathNoArchPrefix(self): 476 arch_dir = {'x86_64': 'x86_64', 'x86_32': 'x86_32'} 477 nmf, nexes = self._CreateDynamicAndStageDeps(arch_dir, 478 nmf_root=self.tempdir, 479 no_arch_prefix=True) 480 expected_manifest = { 481 'files': { 482 'main.nexe': { 483 'x86-32': {'url': 'x86_32/test_dynamic_x86_32.nexe'}, 484 'x86-64': {'url': 'x86_64/test_dynamic_x86_64.nexe'}, 485 }, 486 'libc.so': { 487 'x86-32': {'url': 'x86_32/libc.so'}, 488 'x86-64': {'url': 'x86_64/libc.so'}, 489 }, 490 'libgcc_s.so': { 491 'x86-32': {'url': 'x86_32/libgcc_s.so'}, 492 'x86-64': {'url': 'x86_64/libgcc_s.so'}, 493 }, 494 'libpthread.so': { 495 'x86-32': {'url': 'x86_32/libpthread.so'}, 496 'x86-64': {'url': 'x86_64/libpthread.so'}, 497 }, 498 }, 499 'program': { 500 'x86-32': {'url': 'x86_32/runnable-ld.so'}, 501 'x86-64': {'url': 'x86_64/runnable-ld.so'}, 502 } 503 } 504 505 expected_staging = [PosixRelPath(f, self.tempdir) for f in nexes] 506 expected_staging.extend([ 507 'x86_32/libc.so', 508 'x86_32/libgcc_s.so', 509 'x86_32/libpthread.so', 510 'x86_32/runnable-ld.so', 511 'x86_64/libc.so', 512 'x86_64/libgcc_s.so', 513 'x86_64/libpthread.so', 514 'x86_64/runnable-ld.so']) 515 516 self.assertManifestEquals(nmf, expected_manifest) 517 self.assertStagingEquals(expected_staging) 518 519 def testDynamicWithLibPrefix(self): 520 nmf, nexes = self._CreateDynamicAndStageDeps(lib_prefix='foo') 521 expected_manifest = { 522 'files': { 523 'main.nexe': { 524 'x86-32': {'url': 'test_dynamic_x86_32.nexe'}, 525 'x86-64': {'url': 'test_dynamic_x86_64.nexe'}, 526 }, 527 'libc.so': { 528 'x86-32': {'url': 'foo/lib32/libc.so'}, 529 'x86-64': {'url': 'foo/lib64/libc.so'}, 530 }, 531 'libgcc_s.so': { 532 'x86-32': {'url': 'foo/lib32/libgcc_s.so'}, 533 'x86-64': {'url': 'foo/lib64/libgcc_s.so'}, 534 }, 535 'libpthread.so': { 536 'x86-32': {'url': 'foo/lib32/libpthread.so'}, 537 'x86-64': {'url': 'foo/lib64/libpthread.so'}, 538 }, 539 }, 540 'program': { 541 'x86-32': {'url': 'foo/lib32/runnable-ld.so'}, 542 'x86-64': {'url': 'foo/lib64/runnable-ld.so'}, 543 } 544 } 545 546 expected_staging = [PosixRelPath(f, self.tempdir) for f in nexes] 547 expected_staging.extend([ 548 'foo/lib32/libc.so', 549 'foo/lib32/libgcc_s.so', 550 'foo/lib32/libpthread.so', 551 'foo/lib32/runnable-ld.so', 552 'foo/lib64/libc.so', 553 'foo/lib64/libgcc_s.so', 554 'foo/lib64/libpthread.so', 555 'foo/lib64/runnable-ld.so']) 556 557 self.assertManifestEquals(nmf, expected_manifest) 558 self.assertStagingEquals(expected_staging) 559 560 def testPexe(self): 561 nmf, _ = self._CreatePexe() 562 expected_manifest = { 563 'program': { 564 'portable': { 565 'pnacl-translate': { 566 'url': 'test.pexe' 567 } 568 } 569 } 570 } 571 self.assertManifestEquals(nmf, expected_manifest) 572 573 def testPexeOptLevel(self): 574 nmf, _ = self._CreatePexe(pnacl_optlevel=2) 575 expected_manifest = { 576 'program': { 577 'portable': { 578 'pnacl-translate': { 579 'url': 'test.pexe', 580 'optlevel': 2, 581 } 582 } 583 } 584 } 585 self.assertManifestEquals(nmf, expected_manifest) 586 587 def testBitCode(self): 588 nmf, _ = self._CreateBitCode(pnacl_debug_optlevel=0) 589 expected_manifest = { 590 'program': { 591 'portable': { 592 'pnacl-debug': { 593 'url': 'test.bc', 594 'optlevel': 0, 595 } 596 } 597 } 598 } 599 self.assertManifestEquals(nmf, expected_manifest) 600 601 602if __name__ == '__main__': 603 unittest.main() 604