chromium/native_client_sdk/src/tools/tests/create_nmf_test.py

#!/usr/bin/env vpython3
# Copyright 2012 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import json
import os
import posixpath
import shutil
import subprocess
import sys
import tempfile
import unittest

SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
TOOLS_DIR = os.path.dirname(SCRIPT_DIR)
DATA_DIR = os.path.join(TOOLS_DIR, 'lib', 'tests', 'data')
BUILD_TOOLS_DIR = os.path.join(os.path.dirname(TOOLS_DIR), 'build_tools')
CHROME_SRC = os.path.dirname(os.path.dirname(os.path.dirname(TOOLS_DIR)))

sys.path.append(TOOLS_DIR)
sys.path.append(BUILD_TOOLS_DIR)

import build_paths
import create_nmf
import getos
from mock import patch, Mock

TOOLCHAIN_OUT = os.path.join(build_paths.OUT_DIR, 'sdk_tests', 'toolchain')
X86_GLIBC_TOOLCHAIN = os.path.join(TOOLCHAIN_OUT,
                                   '%s_x86' % getos.GetPlatform(),
                                   'nacl_x86_glibc')
ARM_GLIBC_TOOLCHAIN = os.path.join(TOOLCHAIN_OUT,
                                   '%s_x86' % getos.GetPlatform(),
                                   'nacl_arm_glibc')

PosixRelPath = create_nmf.PosixRelPath


def StripSo(name):
  """Strip trailing hexidecimal characters from the name of a shared object.

  It strips everything after the last '.' in the name, and checks that the new
  name ends with .so.

  e.g.

  libc.so.ad6acbfa => libc.so
  foo.bar.baz => foo.bar.baz
  """
  if '.' in name:
    stripped_name, ext = name.rsplit('.', 1)
    if stripped_name.endswith('.so') and len(ext) > 1:
      return stripped_name
  return name


class TestPosixRelPath(unittest.TestCase):
  def testBasic(self):
    # Note that PosixRelPath only converts from native path format to posix
    # path format, that's why we have to use os.path.join here.
    path = os.path.join(os.path.sep, 'foo', 'bar', 'baz.blah')
    start = os.path.sep + 'foo'
    self.assertEqual(PosixRelPath(path, start), 'bar/baz.blah')


class TestDefaultLibpath(unittest.TestCase):
  def setUp(self):
    patcher = patch('create_nmf.GetSDKRoot', Mock(return_value='/dummy/path'))
    patcher.start()
    self.addCleanup(patcher.stop)

  def testUsesSDKRoot(self):
    paths = create_nmf.GetDefaultLibPath('Debug')
    for path in paths:
      self.assertTrue(path.startswith('/dummy/path'))

  def testFallbackPath(self):
    paths = create_nmf.GetDefaultLibPath('foo_Debug')
    if sys.platform == 'win32':
      paths = [p.replace('\\', '/') for p in paths]
    path_base = '/dummy/path/lib/glibc_x86_64/foo_Debug'
    path_fallback = '/dummy/path/lib/glibc_x86_64/Debug'
    self.assertIn(path_base, paths)
    self.assertIn(path_fallback, paths)
    self.assertGreater(paths.index(path_fallback), paths.index(path_base))

    paths = create_nmf.GetDefaultLibPath('foo_bar')
    if sys.platform == 'win32':
      paths = [p.replace('\\', '/') for p in paths]
    path_base = '/dummy/path/lib/glibc_x86_64/foo_bar'
    path_fallback = '/dummy/path/lib/glibc_x86_64/Release'
    self.assertIn(path_base, paths)
    self.assertIn(path_fallback, paths)
    self.assertGreater(paths.index(path_fallback), paths.index(path_base))


class TestNmfUtils(unittest.TestCase):
  """Tests for the main NmfUtils class in create_nmf."""

  def setUp(self):
    self.tempdir = None
    self.objdump = os.path.join(X86_GLIBC_TOOLCHAIN, 'bin', 'i686-nacl-objdump')
    if os.name == 'nt':
      self.objdump += '.exe'
    self._Mktemp()

    # Create dummy elf_loader_arm.nexe by duplicating an existing so.
    # This nexe is normally build during SDK build but we want these tests
    # to run standalone, and the contents of the ELF are not important for
    # these tests.
    arm_libdir = os.path.join(ARM_GLIBC_TOOLCHAIN, 'arm-nacl', 'lib')
    shutil.copy(os.path.join(arm_libdir, 'ld-nacl-arm.so.1'),
        os.path.join(arm_libdir, 'elf_loader_arm.nexe'))


  def _CreateTestNexe(self, name, arch):
    """Create an empty test .nexe file for use in create_nmf tests.

    This is used rather than checking in test binaries since the
    checked in binaries depend on .so files that only exist in the
    certain SDK that build them.
    """
    if arch == 'arm':
      toolchain = ARM_GLIBC_TOOLCHAIN
    else:
      toolchain = X86_GLIBC_TOOLCHAIN

    compiler = os.path.join(toolchain, 'bin', '%s-nacl-g++' % arch)
    if os.name == 'nt':
      compiler += '.exe'
      os.environ['CYGWIN'] = 'nodosfilewarning'
    program = 'int main() { return 0; }'
    name = os.path.join(self.tempdir, name)
    dst_dir = os.path.dirname(name)
    if not os.path.exists(dst_dir):
      os.makedirs(dst_dir)
    self.assertTrue(os.path.exists(compiler), 'compiler missing: %s' % compiler)
    cmd = [compiler, '-pthread', '-x' , 'c', '-o', name, '-']
    p = subprocess.Popen(cmd, stdin=subprocess.PIPE)
    p.communicate(input=program)
    self.assertEqual(p.returncode, 0)
    return name

  def tearDown(self):
    if self.tempdir:
      shutil.rmtree(self.tempdir)

  def _Mktemp(self):
    self.tempdir = tempfile.mkdtemp()

  def _CreateNmfUtils(self, nexes, **kwargs):
    if not kwargs.get('lib_path'):
      kwargs['lib_path'] = [
          # Use lib instead of lib64 (lib64 is a symlink to lib).
          os.path.join(X86_GLIBC_TOOLCHAIN, 'x86_64-nacl', 'lib'),
          os.path.join(X86_GLIBC_TOOLCHAIN, 'x86_64-nacl', 'lib32'),
          os.path.join(ARM_GLIBC_TOOLCHAIN, 'arm-nacl', 'lib')]
    return create_nmf.NmfUtils(nexes,
                               objdump=self.objdump,
                               **kwargs)

  def _CreateStatic(self, arch_path=None, **kwargs):
    """Copy all static .nexe files from the DATA_DIR to a temporary directory.

    Args:
      arch_path: A dictionary mapping architecture to the directory to generate
          the .nexe for the architecture in.
      kwargs: Keyword arguments to pass through to create_nmf.NmfUtils
          constructor.

    Returns:
      A tuple with 2 elements:
        * The generated NMF as a dictionary (i.e. parsed by json.loads)
        * A list of the generated .nexe paths
    """
    arch_path = arch_path or {}
    nexes = []
    for arch in ('x86_64', 'x86_32', 'arm'):
      nexe_name = 'test_static_%s.nexe' % arch
      src_nexe = os.path.join(DATA_DIR, nexe_name)
      dst_nexe = os.path.join(self.tempdir, arch_path.get(arch, ''), nexe_name)
      dst_dir = os.path.dirname(dst_nexe)
      if not os.path.exists(dst_dir):
        os.makedirs(dst_dir)
      shutil.copy(src_nexe, dst_nexe)
      nexes.append(dst_nexe)

    nexes.sort()
    nmf_utils = self._CreateNmfUtils(nexes, **kwargs)
    nmf = json.loads(nmf_utils.GetJson())
    return nmf, nexes

  def _CreateDynamicAndStageDeps(self, arch_path=None, **kwargs):
    """Create dynamic .nexe files and put them in a temporary directory, with
    their dependencies staged in the same directory.

    Args:
      arch_path: A dictionary mapping architecture to the directory to generate
          the .nexe for the architecture in.
      kwargs: Keyword arguments to pass through to create_nmf.NmfUtils
          constructor.

    Returns:
      A tuple with 2 elements:
        * The generated NMF as a dictionary (i.e. parsed by json.loads)
        * A list of the generated .nexe paths
    """
    arch_path = arch_path or {}
    nexes = []
    for arch in ('x86_64', 'x86_32', 'arm'):
      nexe_name = 'test_dynamic_%s.nexe' % arch
      rel_nexe = os.path.join(arch_path.get(arch, ''), nexe_name)
      arch_alt = 'i686' if arch == 'x86_32' else arch
      nexe = self._CreateTestNexe(rel_nexe, arch_alt)
      nexes.append(nexe)

    nexes.sort()
    nmf_utils = self._CreateNmfUtils(nexes, **kwargs)
    nmf = json.loads(nmf_utils.GetJson())
    nmf_utils.StageDependencies(self.tempdir)

    return nmf, nexes

  def _CreatePexe(self, **kwargs):
    """Copy test.pexe from the DATA_DIR to a temporary directory.

    Args:
      kwargs: Keyword arguments to pass through to create_nmf.NmfUtils
          constructor.

    Returns:
      A tuple with 2 elements:
        * The generated NMF as a dictionary (i.e. parsed by json.loads)
        * A list of the generated .pexe paths
    """
    pexe_name = 'test.pexe'
    src_pexe = os.path.join(DATA_DIR, pexe_name)
    dst_pexe = os.path.join(self.tempdir, pexe_name)
    shutil.copy(src_pexe, dst_pexe)

    pexes = [dst_pexe]
    nmf_utils = self._CreateNmfUtils(pexes, **kwargs)
    nmf = json.loads(nmf_utils.GetJson())

    return nmf, pexes

  def _CreateBitCode(self, **kwargs):
    """Copy test.bc from the DATA_DIR to a temporary directory.

    Args:
      kwargs: Keyword arguments to pass through to create_nmf.NmfUtils
          constructor.

    Returns:
      A tuple with 2 elements:
        * The generated NMF as a dictionary (i.e. parsed by json.loads)
        * A list of the generated .bc paths
    """
    bc_name = 'test.bc'
    src_bc = os.path.join(DATA_DIR, bc_name)
    dst_bc = os.path.join(self.tempdir, bc_name)
    shutil.copy(src_bc, dst_bc)

    bcs = [dst_bc]
    nmf_utils = self._CreateNmfUtils(bcs, **kwargs)
    nmf = json.loads(nmf_utils.GetJson())

    return nmf, bcs

  def assertManifestEquals(self, manifest, expected):
    """Compare two manifest dictionaries.

    The input manifest is regenerated with all string keys and values being
    processed through StripSo, to remove the random hexidecimal characters at
    the end of shared object names.

    Args:
      manifest: The generated manifest.
      expected: The expected manifest.
    """
    def StripSoCopyDict(d):
      new_d = {}
      for k, v in d.iteritems():
        new_k = StripSo(k)
        if isinstance(v, (str, unicode)):
          new_v = StripSo(v)
        elif isinstance(v, list):
          new_v = v[:]
        elif isinstance(v, dict):
          new_v = StripSoCopyDict(v)
        else:
          # Assume that anything else can be copied directly.
          new_v = v

        new_d[new_k] = new_v
      return new_d

    strip_manifest = StripSoCopyDict(manifest)
    self.assertEqual(strip_manifest['program'], expected['program'])
    if 'files' in strip_manifest:
      for key in strip_manifest['files']:
        self.assertEqual(strip_manifest['files'][key], expected['files'][key])

    self.assertEqual(strip_manifest, expected)

  def assertStagingEquals(self, expected):
    """Compare the contents of the temporary directory, to an expected
    directory layout.

    Args:
      expected: The expected directory layout.
    """
    all_files = []
    for root, _, files in os.walk(self.tempdir):
      rel_root_posix = PosixRelPath(root, self.tempdir)
      for f in files:
        path = posixpath.join(rel_root_posix, StripSo(f))
        if path.startswith('./'):
          path = path[2:]
        all_files.append(path)
    self.assertEqual(set(expected), set(all_files))

  arch_dir = {'x86_32': 'x86_32', 'x86_64': 'x86_64', 'arm': 'arm'}

  def testStatic(self):
    nmf, _ = self._CreateStatic()
    expected_manifest = {
      'files': {},
      'program': {
        'x86-64': {'url': 'test_static_x86_64.nexe'},
        'x86-32': {'url': 'test_static_x86_32.nexe'},
        'arm': {'url': 'test_static_arm.nexe'},
      }
    }
    self.assertManifestEquals(nmf, expected_manifest)

  def testStaticWithPath(self):
    nmf, _ = self._CreateStatic(self.arch_dir, nmf_root=self.tempdir)
    expected_manifest = {
      'files': {},
      'program': {
        'x86-32': {'url': 'x86_32/test_static_x86_32.nexe'},
        'x86-64': {'url': 'x86_64/test_static_x86_64.nexe'},
        'arm': {'url': 'arm/test_static_arm.nexe'},
      }
    }
    self.assertManifestEquals(nmf, expected_manifest)

  def testStaticWithPathNoNmfRoot(self):
    # This case is not particularly useful, but it is similar to how create_nmf
    # used to work. If there is no nmf_root given, all paths are relative to
    # the first nexe passed on the commandline. I believe the assumption
    # previously was that all .nexes would be in the same directory.
    nmf, _ = self._CreateStatic(self.arch_dir)
    expected_manifest = {
      'files': {},
      'program': {
        'x86-32': {'url': '../x86_32/test_static_x86_32.nexe'},
        'x86-64': {'url': '../x86_64/test_static_x86_64.nexe'},
        'arm': {'url': 'test_static_arm.nexe'},
      }
    }
    self.assertManifestEquals(nmf, expected_manifest)

  def testStaticWithNexePrefix(self):
    nmf, _ = self._CreateStatic(nexe_prefix='foo')
    expected_manifest = {
      'files': {},
      'program': {
        'x86-64': {'url': 'foo/test_static_x86_64.nexe'},
        'x86-32': {'url': 'foo/test_static_x86_32.nexe'},
        'arm': {'url': 'foo/test_static_arm.nexe'},
      }
    }
    self.assertManifestEquals(nmf, expected_manifest)

  def testDynamic(self):
    nmf, nexes = self._CreateDynamicAndStageDeps()
    expected_manifest = {
      'files': {
        'main.nexe': {
          'x86-32': {'url': 'test_dynamic_x86_32.nexe'},
          'x86-64': {'url': 'test_dynamic_x86_64.nexe'},
          'arm': {'url': 'test_dynamic_arm.nexe'},
        },
        'ld-nacl-arm.so.1': {
          'arm': {'url': 'libarm/ld-nacl-arm.so.1'},
        },
        'libc.so.0.1': {
          'arm': {'url': 'libarm/libc.so.0.1'}
        },
        'libc.so': {
          'x86-32': {'url': 'lib32/libc.so'},
          'x86-64': {'url': 'lib64/libc.so'},
        },
        'libgcc_s.so.1': {
          'arm': {'url': 'libarm/libgcc_s.so.1'},
          'x86-32': {'url': 'lib32/libgcc_s.so.1'},
          'x86-64': {'url': 'lib64/libgcc_s.so.1'},
        },
        'libpthread.so.0': {
          'arm': { 'url': 'libarm/libpthread.so.0'}
        },
        'libpthread.so': {
          'x86-32': {'url': 'lib32/libpthread.so'},
          'x86-64': {'url': 'lib64/libpthread.so'},
        },
      },
      'program': {
        'arm': {'url': 'libarm/elf_loader_arm.nexe'},
        'x86-32': {'url': 'lib32/runnable-ld.so'},
        'x86-64': {'url': 'lib64/runnable-ld.so'},
      }
    }

    expected_staging = [os.path.basename(f) for f in nexes]
    expected_staging.extend([
      'lib32/libc.so',
      'lib32/libgcc_s.so.1',
      'lib32/libpthread.so',
      'lib32/runnable-ld.so',
      'lib64/libc.so',
      'lib64/libgcc_s.so.1',
      'lib64/libpthread.so',
      'lib64/runnable-ld.so',
      'libarm/elf_loader_arm.nexe',
      'libarm/libpthread.so.0',
      'libarm/ld-nacl-arm.so.1',
      'libarm/libgcc_s.so.1',
      'libarm/libc.so.0.1'
    ])

    self.assertManifestEquals(nmf, expected_manifest)
    self.assertStagingEquals(expected_staging)

  def testDynamicWithPath(self):
    nmf, nexes = self._CreateDynamicAndStageDeps(self.arch_dir,
                                                 nmf_root=self.tempdir)
    expected_manifest = {
      'files': {
        'main.nexe': {
          'arm': {'url': 'arm/test_dynamic_arm.nexe'},
          'x86-32': {'url': 'x86_32/test_dynamic_x86_32.nexe'},
          'x86-64': {'url': 'x86_64/test_dynamic_x86_64.nexe'},
        },
        'libc.so.0.1': {
          'arm': {'url': 'arm/libarm/libc.so.0.1'}
        },
        'ld-nacl-arm.so.1': {
          'arm': {'url': 'arm/libarm/ld-nacl-arm.so.1'},
        },
        'libc.so': {
          'x86-32': {'url': 'x86_32/lib32/libc.so'},
          'x86-64': {'url': 'x86_64/lib64/libc.so'},
        },
        'libgcc_s.so.1': {
          'arm': {'url': 'arm/libarm/libgcc_s.so.1'},
          'x86-32': {'url': 'x86_32/lib32/libgcc_s.so.1'},
          'x86-64': {'url': 'x86_64/lib64/libgcc_s.so.1'},
        },
        'libpthread.so.0': {
          'arm': { 'url': 'arm/libarm/libpthread.so.0'}
        },
        'libpthread.so': {
          'x86-32': {'url': 'x86_32/lib32/libpthread.so'},
          'x86-64': {'url': 'x86_64/lib64/libpthread.so'},
        },
      },
      'program': {
        'arm': {'url': 'arm/libarm/elf_loader_arm.nexe'},
        'x86-32': {'url': 'x86_32/lib32/runnable-ld.so'},
        'x86-64': {'url': 'x86_64/lib64/runnable-ld.so'},
      }
    }

    expected_staging = [PosixRelPath(f, self.tempdir) for f in nexes]
    expected_staging.extend([
      'x86_32/lib32/libc.so',
      'x86_32/lib32/libgcc_s.so.1',
      'x86_32/lib32/libpthread.so',
      'x86_32/lib32/runnable-ld.so',
      'x86_64/lib64/libc.so',
      'x86_64/lib64/libgcc_s.so.1',
      'x86_64/lib64/libpthread.so',
      'x86_64/lib64/runnable-ld.so',
      'arm/libarm/elf_loader_arm.nexe',
      'arm/libarm/libpthread.so.0',
      'arm/libarm/ld-nacl-arm.so.1',
      'arm/libarm/libgcc_s.so.1',
      'arm/libarm/libc.so.0.1'
    ])

    self.assertManifestEquals(nmf, expected_manifest)
    self.assertStagingEquals(expected_staging)

  def testDynamicWithRelPath(self):
    """Test that when the nmf root is a relative path that things work."""
    old_path = os.getcwd()
    try:
      os.chdir(self.tempdir)
      nmf, nexes = self._CreateDynamicAndStageDeps(self.arch_dir, nmf_root='')
      expected_manifest = {
        'files': {
          'main.nexe': {
            'arm': {'url': 'arm/test_dynamic_arm.nexe'},
            'x86-32': {'url': 'x86_32/test_dynamic_x86_32.nexe'},
            'x86-64': {'url': 'x86_64/test_dynamic_x86_64.nexe'},
          },
          'ld-nacl-arm.so.1': {
            'arm': {'url': 'arm/libarm/ld-nacl-arm.so.1'},
          },
          'libc.so.0.1': {
            'arm': {'url': 'arm/libarm/libc.so.0.1'}
          },
          'libc.so': {
            'x86-32': {'url': 'x86_32/lib32/libc.so'},
            'x86-64': {'url': 'x86_64/lib64/libc.so'},
          },
          'libgcc_s.so.1': {
            'arm': {'url': 'arm/libarm/libgcc_s.so.1'},
            'x86-32': {'url': 'x86_32/lib32/libgcc_s.so.1'},
            'x86-64': {'url': 'x86_64/lib64/libgcc_s.so.1'},
          },
          'libpthread.so.0': {
            'arm': { 'url': 'arm/libarm/libpthread.so.0'}
          },
          'libpthread.so': {
            'x86-32': {'url': 'x86_32/lib32/libpthread.so'},
            'x86-64': {'url': 'x86_64/lib64/libpthread.so'},
          },
        },
        'program': {
          'arm': {'url': 'arm/libarm/elf_loader_arm.nexe'},
          'x86-32': {'url': 'x86_32/lib32/runnable-ld.so'},
          'x86-64': {'url': 'x86_64/lib64/runnable-ld.so'},
        }
      }

      expected_staging = [PosixRelPath(f, self.tempdir) for f in nexes]
      expected_staging.extend([
        'x86_32/lib32/libc.so',
        'x86_32/lib32/libgcc_s.so.1',
        'x86_32/lib32/libpthread.so',
        'x86_32/lib32/runnable-ld.so',
        'x86_64/lib64/libc.so',
        'x86_64/lib64/libgcc_s.so.1',
        'x86_64/lib64/libpthread.so',
        'x86_64/lib64/runnable-ld.so',
        'arm/libarm/elf_loader_arm.nexe',
        'arm/libarm/libpthread.so.0',
        'arm/libarm/ld-nacl-arm.so.1',
        'arm/libarm/libgcc_s.so.1',
        'arm/libarm/libc.so.0.1'
      ])

      self.assertManifestEquals(nmf, expected_manifest)
      self.assertStagingEquals(expected_staging)
    finally:
      os.chdir(old_path)

  def testDynamicWithPathNoArchPrefix(self):
    nmf, nexes = self._CreateDynamicAndStageDeps(self.arch_dir,
                                                 nmf_root=self.tempdir,
                                                 no_arch_prefix=True)
    expected_manifest = {
      'files': {
        'main.nexe': {
          'arm': {'url': 'arm/test_dynamic_arm.nexe'},
          'x86-32': {'url': 'x86_32/test_dynamic_x86_32.nexe'},
          'x86-64': {'url': 'x86_64/test_dynamic_x86_64.nexe'},
        },
        'ld-nacl-arm.so.1': {
          'arm': {'url': 'arm/ld-nacl-arm.so.1'},
        },
        'libc.so.0.1': {
          'arm': {'url': 'arm/libc.so.0.1'}
        },
        'libc.so': {
          'x86-32': {'url': 'x86_32/libc.so'},
          'x86-64': {'url': 'x86_64/libc.so'},
        },
        'libgcc_s.so.1': {
          'arm': {'url': 'arm/libgcc_s.so.1'},
          'x86-32': {'url': 'x86_32/libgcc_s.so.1'},
          'x86-64': {'url': 'x86_64/libgcc_s.so.1'},
        },
        'libpthread.so.0': {
          'arm': { 'url': 'arm/libpthread.so.0'}
        },
        'libpthread.so': {
          'x86-32': {'url': 'x86_32/libpthread.so'},
          'x86-64': {'url': 'x86_64/libpthread.so'},
        },
      },
      'program': {
        'arm': {'url': 'arm/elf_loader_arm.nexe'},
        'x86-32': {'url': 'x86_32/runnable-ld.so'},
        'x86-64': {'url': 'x86_64/runnable-ld.so'},
      }
    }

    expected_staging = [PosixRelPath(f, self.tempdir) for f in nexes]
    expected_staging.extend([
      'x86_32/libc.so',
      'x86_32/libgcc_s.so.1',
      'x86_32/libpthread.so',
      'x86_32/runnable-ld.so',
      'x86_64/libc.so',
      'x86_64/libgcc_s.so.1',
      'x86_64/libpthread.so',
      'x86_64/runnable-ld.so',
      'arm/elf_loader_arm.nexe',
      'arm/libpthread.so.0',
      'arm/ld-nacl-arm.so.1',
      'arm/libgcc_s.so.1',
      'arm/libc.so.0.1'
    ])

    self.assertManifestEquals(nmf, expected_manifest)
    self.assertStagingEquals(expected_staging)

  def testDynamicWithLibPrefix(self):
    nmf, nexes = self._CreateDynamicAndStageDeps(lib_prefix='foo')
    expected_manifest = {
      'files': {
        'main.nexe': {
          'arm': {'url': 'test_dynamic_arm.nexe'},
          'x86-32': {'url': 'test_dynamic_x86_32.nexe'},
          'x86-64': {'url': 'test_dynamic_x86_64.nexe'},
        },
        'ld-nacl-arm.so.1': {
          'arm': {'url': 'foo/libarm/ld-nacl-arm.so.1'},
        },
        'libc.so.0.1': {
          'arm': {'url': 'foo/libarm/libc.so.0.1'}
        },
        'libc.so': {
          'x86-32': {'url': 'foo/lib32/libc.so'},
          'x86-64': {'url': 'foo/lib64/libc.so'},
        },
        'libgcc_s.so.1': {
          'arm': {'url': 'foo/libarm/libgcc_s.so.1'},
          'x86-32': {'url': 'foo/lib32/libgcc_s.so.1'},
          'x86-64': {'url': 'foo/lib64/libgcc_s.so.1'},
        },
        'libpthread.so.0': {
          'arm': { 'url': 'foo/libarm/libpthread.so.0'}
        },
        'libpthread.so': {
          'x86-32': {'url': 'foo/lib32/libpthread.so'},
          'x86-64': {'url': 'foo/lib64/libpthread.so'},
        },
      },
      'program': {
        'arm': {'url': 'foo/libarm/elf_loader_arm.nexe'},
        'x86-32': {'url': 'foo/lib32/runnable-ld.so'},
        'x86-64': {'url': 'foo/lib64/runnable-ld.so'},
      }
    }

    expected_staging = [PosixRelPath(f, self.tempdir) for f in nexes]
    expected_staging.extend([
      'foo/lib32/libc.so',
      'foo/lib32/libgcc_s.so.1',
      'foo/lib32/libpthread.so',
      'foo/lib32/runnable-ld.so',
      'foo/lib64/libc.so',
      'foo/lib64/libgcc_s.so.1',
      'foo/lib64/libpthread.so',
      'foo/lib64/runnable-ld.so',
      'foo/libarm/elf_loader_arm.nexe',
      'foo/libarm/libpthread.so.0',
      'foo/libarm/ld-nacl-arm.so.1',
      'foo/libarm/libgcc_s.so.1',
      'foo/libarm/libc.so.0.1'
    ])

    self.assertManifestEquals(nmf, expected_manifest)
    self.assertStagingEquals(expected_staging)

  def testPexe(self):
    nmf, _ = self._CreatePexe()
    expected_manifest = {
      'program': {
        'portable': {
          'pnacl-translate': {
            'url': 'test.pexe'
          }
        }
      }
    }
    self.assertManifestEquals(nmf, expected_manifest)

  def testPexeOptLevel(self):
    nmf, _ = self._CreatePexe(pnacl_optlevel=2)
    expected_manifest = {
      'program': {
        'portable': {
          'pnacl-translate': {
            'url': 'test.pexe',
            'optlevel': 2,
          }
        }
      }
    }
    self.assertManifestEquals(nmf, expected_manifest)

  def testBitCode(self):
    nmf, _ = self._CreateBitCode(pnacl_debug_optlevel=0)
    expected_manifest = {
      'program': {
        'portable': {
          'pnacl-debug': {
            'url': 'test.bc',
            'optlevel': 0,
          }
        }
      }
    }
    self.assertManifestEquals(nmf, expected_manifest)


if __name__ == '__main__':
  unittest.main()