Historically we built all our binaries in directories in the objdir, then symlinked them into dist/bin. Some binaries needed to be copied instead so that certain relative path lookups work properly, so we resorted to sprinkling `NSDISTMODE=copy` around Makefiles. This change makes it so we build PROGRAMs (not any other sort of targets) directly in dist/bin instead. We could do the same for our other targets with a little more work. There were several places in the tree that were copying built binaries to some other place and needed fixup to match the new location of binaries. On Windows pdb files are left in the objdir where the program was originally linked. symbolstore.py needs to locate the pdb file both to determine whether it should dump symbols for a binary and also to copy the pdb file into the symbol package. We fix this by simply looking for the pdb file in the current working directory if it isn't present next to the binary, which matches how we invoke symbolstore.py. MozReview-Commit-ID: 8TOD1uTXD5e
545 lines
23 KiB
Python
Executable File
545 lines
23 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
import concurrent.futures
|
|
import mock
|
|
import mozunit
|
|
import os
|
|
import platform
|
|
import shutil
|
|
import struct
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
import buildconfig
|
|
|
|
from mock import patch
|
|
from mozpack.manifests import InstallManifest
|
|
import mozpack.path as mozpath
|
|
|
|
import symbolstore
|
|
from symbolstore import normpath
|
|
|
|
# Some simple functions to mock out files that the platform-specific dumpers will accept.
|
|
# dump_syms itself will not be run (we mock that call out), but we can't override
|
|
# the ShouldProcessFile method since we actually want to test that.
|
|
def write_elf(filename):
|
|
open(filename, "wb").write(struct.pack("<7B45x", 0x7f, ord("E"), ord("L"), ord("F"), 1, 1, 1))
|
|
|
|
def write_macho(filename):
|
|
open(filename, "wb").write(struct.pack("<I28x", 0xfeedface))
|
|
|
|
def write_dll(filename):
|
|
open(filename, "w").write("aaa")
|
|
# write out a fake PDB too
|
|
open(os.path.splitext(filename)[0] + ".pdb", "w").write("aaa")
|
|
|
|
def target_platform():
|
|
return buildconfig.substs['OS_TARGET']
|
|
|
|
writer = {'WINNT': write_dll,
|
|
'Linux': write_elf,
|
|
'Sunos5': write_elf,
|
|
'Darwin': write_macho}[target_platform()]
|
|
extension = {'WINNT': ".dll",
|
|
'Linux': ".so",
|
|
'Sunos5': ".so",
|
|
'Darwin': ".dylib"}[target_platform()]
|
|
file_output = [{'WINNT': "bogus data",
|
|
'Linux': "ELF executable",
|
|
'Darwin': "Mach-O executable"}[target_platform()]]
|
|
|
|
def add_extension(files):
|
|
return [f + extension for f in files]
|
|
|
|
class HelperMixin(object):
|
|
"""
|
|
Test that passing filenames to exclude from processing works.
|
|
"""
|
|
def setUp(self):
|
|
self.test_dir = tempfile.mkdtemp()
|
|
if not self.test_dir.endswith(os.sep):
|
|
self.test_dir += os.sep
|
|
symbolstore.srcdirRepoInfo = {}
|
|
symbolstore.vcsFileInfoCache = {}
|
|
|
|
# Remove environment variables that can influence tests.
|
|
for e in ('MOZ_SOURCE_CHANGESET', 'MOZ_SOURCE_REPO'):
|
|
try:
|
|
del os.environ[e]
|
|
except KeyError:
|
|
pass
|
|
|
|
def tearDown(self):
|
|
shutil.rmtree(self.test_dir)
|
|
symbolstore.srcdirRepoInfo = {}
|
|
symbolstore.vcsFileInfoCache = {}
|
|
|
|
def make_dirs(self, f):
|
|
d = os.path.dirname(f)
|
|
if d and not os.path.exists(d):
|
|
os.makedirs(d)
|
|
|
|
def make_file(self, path):
|
|
self.make_dirs(path)
|
|
with open(path, 'wb') as f:
|
|
pass
|
|
|
|
def add_test_files(self, files):
|
|
for f in files:
|
|
f = os.path.join(self.test_dir, f)
|
|
self.make_dirs(f)
|
|
writer(f)
|
|
|
|
|
|
def mock_dump_syms(module_id, filename, extra=[]):
|
|
return ["MODULE os x86 %s %s" % (module_id, filename)
|
|
] + extra + [
|
|
"FILE 0 foo.c",
|
|
"PUBLIC xyz 123"]
|
|
|
|
|
|
class TestCopyDebug(HelperMixin, unittest.TestCase):
|
|
def setUp(self):
|
|
HelperMixin.setUp(self)
|
|
self.symbol_dir = tempfile.mkdtemp()
|
|
self.mock_call = patch("subprocess.call").start()
|
|
self.stdouts = []
|
|
self.mock_popen = patch("subprocess.Popen").start()
|
|
stdout_iter = self.next_mock_stdout()
|
|
def next_popen(*args, **kwargs):
|
|
m = mock.MagicMock()
|
|
# Get the iterators over whatever output was provided.
|
|
stdout_ = stdout_iter.next()
|
|
# Eager evaluation for communicate(), below.
|
|
stdout_ = list(stdout_)
|
|
# stdout is really an iterator, so back to iterators we go.
|
|
m.stdout = iter(stdout_)
|
|
m.wait.return_value = 0
|
|
# communicate returns the full text of stdout and stderr.
|
|
m.communicate.return_value = ('\n'.join(stdout_), '')
|
|
return m
|
|
self.mock_popen.side_effect = next_popen
|
|
shutil.rmtree = patch("shutil.rmtree").start()
|
|
|
|
def tearDown(self):
|
|
HelperMixin.tearDown(self)
|
|
patch.stopall()
|
|
shutil.rmtree(self.symbol_dir)
|
|
|
|
def next_mock_stdout(self):
|
|
if not self.stdouts:
|
|
yield iter([])
|
|
for s in self.stdouts:
|
|
yield iter(s)
|
|
|
|
def test_copy_debug_universal(self):
|
|
"""
|
|
Test that dumping symbols for multiple architectures only copies debug symbols once
|
|
per file.
|
|
"""
|
|
copied = []
|
|
def mock_copy_debug(filename, debug_file, guid, code_file, code_id):
|
|
copied.append(filename[len(self.symbol_dir):] if filename.startswith(self.symbol_dir) else filename)
|
|
self.add_test_files(add_extension(["foo"]))
|
|
# Windows doesn't call file(1) to figure out if the file should be processed.
|
|
if target_platform() != 'WINNT':
|
|
self.stdouts.append(file_output)
|
|
self.stdouts.append(mock_dump_syms("X" * 33, add_extension(["foo"])[0]))
|
|
self.stdouts.append(mock_dump_syms("Y" * 33, add_extension(["foo"])[0]))
|
|
def mock_dsymutil(args, **kwargs):
|
|
filename = args[-1]
|
|
os.makedirs(filename + ".dSYM")
|
|
return 0
|
|
self.mock_call.side_effect = mock_dsymutil
|
|
d = symbolstore.GetPlatformSpecificDumper(dump_syms="dump_syms",
|
|
symbol_path=self.symbol_dir,
|
|
copy_debug=True,
|
|
archs="abc xyz")
|
|
d.CopyDebug = mock_copy_debug
|
|
d.Process(os.path.join(self.test_dir, add_extension(["foo"])[0]))
|
|
self.assertEqual(1, len(copied))
|
|
|
|
@patch.dict('buildconfig.substs._dict', {'MAKECAB': 'makecab'})
|
|
def test_copy_debug_copies_binaries(self):
|
|
"""
|
|
Test that CopyDebug copies binaries as well on Windows.
|
|
"""
|
|
test_file = os.path.join(self.test_dir, 'foo.dll')
|
|
write_dll(test_file)
|
|
code_file = 'foo.dll'
|
|
code_id = 'abc123'
|
|
self.stdouts.append(mock_dump_syms('X' * 33, 'foo.pdb',
|
|
['INFO CODE_ID %s %s' % (code_id, code_file)]))
|
|
def mock_compress(args, **kwargs):
|
|
filename = args[-1]
|
|
open(filename, 'w').write('stuff')
|
|
return 0
|
|
self.mock_call.side_effect = mock_compress
|
|
d = symbolstore.Dumper_Win32(dump_syms='dump_syms',
|
|
symbol_path=self.symbol_dir,
|
|
copy_debug=True)
|
|
d.Process(test_file)
|
|
self.assertTrue(os.path.isfile(os.path.join(self.symbol_dir, code_file, code_id, code_file[:-1] + '_')))
|
|
|
|
class TestGetVCSFilename(HelperMixin, unittest.TestCase):
|
|
def setUp(self):
|
|
HelperMixin.setUp(self)
|
|
|
|
def tearDown(self):
|
|
HelperMixin.tearDown(self)
|
|
|
|
@patch("subprocess.Popen")
|
|
def testVCSFilenameHg(self, mock_Popen):
|
|
# mock calls to `hg parent` and `hg showconfig paths.default`
|
|
mock_communicate = mock_Popen.return_value.communicate
|
|
mock_communicate.side_effect = [("abcd1234", ""),
|
|
("http://example.com/repo", "")]
|
|
os.mkdir(os.path.join(self.test_dir, ".hg"))
|
|
filename = os.path.join(self.test_dir, "foo.c")
|
|
self.assertEqual("hg:example.com/repo:foo.c:abcd1234",
|
|
symbolstore.GetVCSFilename(filename, [self.test_dir])[0])
|
|
|
|
@patch("subprocess.Popen")
|
|
def testVCSFilenameHgMultiple(self, mock_Popen):
|
|
# mock calls to `hg parent` and `hg showconfig paths.default`
|
|
mock_communicate = mock_Popen.return_value.communicate
|
|
mock_communicate.side_effect = [("abcd1234", ""),
|
|
("http://example.com/repo", ""),
|
|
("0987ffff", ""),
|
|
("http://example.com/other", "")]
|
|
srcdir1 = os.path.join(self.test_dir, "one")
|
|
srcdir2 = os.path.join(self.test_dir, "two")
|
|
os.makedirs(os.path.join(srcdir1, ".hg"))
|
|
os.makedirs(os.path.join(srcdir2, ".hg"))
|
|
filename1 = os.path.join(srcdir1, "foo.c")
|
|
filename2 = os.path.join(srcdir2, "bar.c")
|
|
self.assertEqual("hg:example.com/repo:foo.c:abcd1234",
|
|
symbolstore.GetVCSFilename(filename1, [srcdir1, srcdir2])[0])
|
|
self.assertEqual("hg:example.com/other:bar.c:0987ffff",
|
|
symbolstore.GetVCSFilename(filename2, [srcdir1, srcdir2])[0])
|
|
|
|
def testVCSFilenameEnv(self):
|
|
# repo URL and changeset read from environment variables if defined.
|
|
os.environ['MOZ_SOURCE_REPO'] = 'https://somewhere.com/repo'
|
|
os.environ['MOZ_SOURCE_CHANGESET'] = 'abcdef0123456'
|
|
os.mkdir(os.path.join(self.test_dir, '.hg'))
|
|
filename = os.path.join(self.test_dir, 'foo.c')
|
|
self.assertEqual('hg:somewhere.com/repo:foo.c:abcdef0123456',
|
|
symbolstore.GetVCSFilename(filename, [self.test_dir])[0])
|
|
|
|
|
|
# SHA-512 of a zero-byte file
|
|
EMPTY_SHA512 = 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e'
|
|
|
|
|
|
class TestGeneratedFilePath(HelperMixin, unittest.TestCase):
|
|
def setUp(self):
|
|
HelperMixin.setUp(self)
|
|
|
|
def tearDown(self):
|
|
HelperMixin.tearDown(self)
|
|
|
|
def test_generated_file_path(self):
|
|
# Make an empty generated file
|
|
g = os.path.join(self.test_dir, 'generated')
|
|
rel_path = 'a/b/generated'
|
|
with open(g, 'wb') as f:
|
|
pass
|
|
expected = 's3:bucket:{}/{}:'.format(EMPTY_SHA512,
|
|
rel_path)
|
|
self.assertEqual(expected, symbolstore.get_generated_file_s3_path(g, rel_path, 'bucket'))
|
|
|
|
|
|
if target_platform() == 'WINNT':
|
|
class TestNormpath(HelperMixin, unittest.TestCase):
|
|
def test_normpath(self):
|
|
# self.test_dir is going to be 8.3 paths...
|
|
junk = os.path.join(self.test_dir, 'x')
|
|
with open(junk, 'wb') as o:
|
|
o.write('x')
|
|
fixed_dir = os.path.dirname(normpath(junk))
|
|
files = [
|
|
'one\\two.c',
|
|
'three\\Four.d',
|
|
'Five\\Six.e',
|
|
'seven\\Eight\\nine.F',
|
|
]
|
|
for rel_path in files:
|
|
full_path = os.path.normpath(os.path.join(self.test_dir,
|
|
rel_path))
|
|
self.make_dirs(full_path)
|
|
with open(full_path, 'wb') as o:
|
|
o.write('x')
|
|
fixed_path = normpath(full_path.lower())
|
|
fixed_path = os.path.relpath(fixed_path, fixed_dir)
|
|
self.assertEqual(rel_path, fixed_path)
|
|
|
|
class TestSourceServer(HelperMixin, unittest.TestCase):
|
|
@patch("subprocess.call")
|
|
@patch("subprocess.Popen")
|
|
def test_HGSERVER(self, mock_Popen, mock_call):
|
|
"""
|
|
Test that HGSERVER gets set correctly in the source server index.
|
|
"""
|
|
symbolpath = os.path.join(self.test_dir, "symbols")
|
|
os.makedirs(symbolpath)
|
|
srcdir = os.path.join(self.test_dir, "srcdir")
|
|
os.makedirs(os.path.join(srcdir, ".hg"))
|
|
sourcefile = os.path.join(srcdir, "foo.c")
|
|
test_files = add_extension(["foo"])
|
|
self.add_test_files(test_files)
|
|
# srcsrv needs PDBSTR_PATH set
|
|
os.environ["PDBSTR_PATH"] = "pdbstr"
|
|
# mock calls to `dump_syms`, `hg parent` and
|
|
# `hg showconfig paths.default`
|
|
mock_Popen.return_value.stdout = iter([
|
|
"MODULE os x86 %s %s" % ("X" * 33, test_files[0]),
|
|
"FILE 0 %s" % sourcefile,
|
|
"PUBLIC xyz 123"
|
|
])
|
|
mock_communicate = mock_Popen.return_value.communicate
|
|
mock_communicate.side_effect = [("abcd1234", ""),
|
|
("http://example.com/repo", ""),
|
|
]
|
|
# And mock the call to pdbstr to capture the srcsrv stream data.
|
|
global srcsrv_stream
|
|
srcsrv_stream = None
|
|
def mock_pdbstr(args, cwd="", **kwargs):
|
|
for arg in args:
|
|
if arg.startswith("-i:"):
|
|
global srcsrv_stream
|
|
srcsrv_stream = open(os.path.join(cwd, arg[3:]), 'r').read()
|
|
return 0
|
|
mock_call.side_effect = mock_pdbstr
|
|
d = symbolstore.GetPlatformSpecificDumper(dump_syms="dump_syms",
|
|
symbol_path=symbolpath,
|
|
srcdirs=[srcdir],
|
|
vcsinfo=True,
|
|
srcsrv=True,
|
|
copy_debug=True)
|
|
# stub out CopyDebug
|
|
d.CopyDebug = lambda *args: True
|
|
d.Process(os.path.join(self.test_dir, test_files[0]))
|
|
self.assertNotEqual(srcsrv_stream, None)
|
|
hgserver = [x.rstrip() for x in srcsrv_stream.splitlines() if x.startswith("HGSERVER=")]
|
|
self.assertEqual(len(hgserver), 1)
|
|
self.assertEqual(hgserver[0].split("=")[1], "http://example.com/repo")
|
|
|
|
class TestInstallManifest(HelperMixin, unittest.TestCase):
|
|
def setUp(self):
|
|
HelperMixin.setUp(self)
|
|
self.srcdir = os.path.join(self.test_dir, 'src')
|
|
os.mkdir(self.srcdir)
|
|
self.objdir = os.path.join(self.test_dir, 'obj')
|
|
os.mkdir(self.objdir)
|
|
self.manifest = InstallManifest()
|
|
self.canonical_mapping = {}
|
|
for s in ['src1', 'src2']:
|
|
srcfile = normpath(os.path.join(self.srcdir, s))
|
|
objfile = normpath(os.path.join(self.objdir, s))
|
|
self.canonical_mapping[objfile] = srcfile
|
|
self.manifest.add_copy(srcfile, s)
|
|
self.manifest_file = os.path.join(self.test_dir, 'install-manifest')
|
|
self.manifest.write(self.manifest_file)
|
|
|
|
def testMakeFileMapping(self):
|
|
'''
|
|
Test that valid arguments are validated.
|
|
'''
|
|
arg = '%s,%s' % (self.manifest_file, self.objdir)
|
|
ret = symbolstore.validate_install_manifests([arg])
|
|
self.assertEqual(len(ret), 1)
|
|
manifest, dest = ret[0]
|
|
self.assertTrue(isinstance(manifest, InstallManifest))
|
|
self.assertEqual(dest, self.objdir)
|
|
|
|
file_mapping = symbolstore.make_file_mapping(ret)
|
|
for obj, src in self.canonical_mapping.iteritems():
|
|
self.assertTrue(obj in file_mapping)
|
|
self.assertEqual(file_mapping[obj], src)
|
|
|
|
def testMissingFiles(self):
|
|
'''
|
|
Test that missing manifest files or install directories give errors.
|
|
'''
|
|
missing_manifest = os.path.join(self.test_dir, 'missing-manifest')
|
|
arg = '%s,%s' % (missing_manifest, self.objdir)
|
|
with self.assertRaises(IOError) as e:
|
|
symbolstore.validate_install_manifests([arg])
|
|
self.assertEqual(e.filename, missing_manifest)
|
|
|
|
missing_install_dir = os.path.join(self.test_dir, 'missing-dir')
|
|
arg = '%s,%s' % (self.manifest_file, missing_install_dir)
|
|
with self.assertRaises(IOError) as e:
|
|
symbolstore.validate_install_manifests([arg])
|
|
self.assertEqual(e.filename, missing_install_dir)
|
|
|
|
def testBadManifest(self):
|
|
'''
|
|
Test that a bad manifest file give errors.
|
|
'''
|
|
bad_manifest = os.path.join(self.test_dir, 'bad-manifest')
|
|
with open(bad_manifest, 'wb') as f:
|
|
f.write('junk\n')
|
|
arg = '%s,%s' % (bad_manifest, self.objdir)
|
|
with self.assertRaises(IOError) as e:
|
|
symbolstore.validate_install_manifests([arg])
|
|
self.assertEqual(e.filename, bad_manifest)
|
|
|
|
def testBadArgument(self):
|
|
'''
|
|
Test that a bad manifest argument gives an error.
|
|
'''
|
|
with self.assertRaises(ValueError) as e:
|
|
symbolstore.validate_install_manifests(['foo'])
|
|
|
|
class TestFileMapping(HelperMixin, unittest.TestCase):
|
|
def setUp(self):
|
|
HelperMixin.setUp(self)
|
|
self.srcdir = os.path.join(self.test_dir, 'src')
|
|
os.mkdir(self.srcdir)
|
|
self.objdir = os.path.join(self.test_dir, 'obj')
|
|
os.mkdir(self.objdir)
|
|
self.symboldir = os.path.join(self.test_dir, 'symbols')
|
|
os.mkdir(self.symboldir)
|
|
|
|
@patch("subprocess.Popen")
|
|
def testFileMapping(self, mock_Popen):
|
|
files = [('a/b', 'mozilla/b'),
|
|
('c/d', 'foo/d')]
|
|
if os.sep != '/':
|
|
files = [[f.replace('/', os.sep) for f in x] for x in files]
|
|
file_mapping = {}
|
|
dumped_files = []
|
|
expected_files = []
|
|
self.make_dirs(os.path.join(self.objdir, 'x', 'y'))
|
|
for s, o in files:
|
|
srcfile = os.path.join(self.srcdir, s)
|
|
self.make_file(srcfile)
|
|
expected_files.append(normpath(srcfile))
|
|
objfile = os.path.join(self.objdir, o)
|
|
self.make_file(objfile)
|
|
file_mapping[normpath(objfile)] = normpath(srcfile)
|
|
dumped_files.append(os.path.join(self.objdir, 'x', 'y',
|
|
'..', '..', o))
|
|
# mock the dump_syms output
|
|
file_id = ("X" * 33, 'somefile')
|
|
def mk_output(files):
|
|
return iter(
|
|
[
|
|
'MODULE os x86 %s %s\n' % file_id
|
|
] +
|
|
[
|
|
'FILE %d %s\n' % (i,s) for i, s in enumerate(files)
|
|
] +
|
|
[
|
|
'PUBLIC xyz 123\n'
|
|
]
|
|
)
|
|
mock_Popen.return_value.stdout = mk_output(dumped_files)
|
|
|
|
d = symbolstore.Dumper('dump_syms', self.symboldir,
|
|
file_mapping=file_mapping)
|
|
f = os.path.join(self.objdir, 'somefile')
|
|
open(f, 'wb').write('blah')
|
|
d.Process(f)
|
|
expected_output = ''.join(mk_output(expected_files))
|
|
symbol_file = os.path.join(self.symboldir,
|
|
file_id[1], file_id[0], file_id[1] + '.sym')
|
|
self.assertEqual(open(symbol_file, 'r').read(), expected_output)
|
|
|
|
class TestFunctional(HelperMixin, unittest.TestCase):
|
|
'''Functional tests of symbolstore.py, calling it with a real
|
|
dump_syms binary and passing in a real binary to dump symbols from.
|
|
|
|
Since the rest of the tests in this file mock almost everything and
|
|
don't use the actual process pool like buildsymbols does, this tests
|
|
that the way symbolstore.py gets called in buildsymbols works.
|
|
'''
|
|
def setUp(self):
|
|
HelperMixin.setUp(self)
|
|
self.skip_test = False
|
|
if buildconfig.substs['MOZ_BUILD_APP'] != 'browser':
|
|
self.skip_test = True
|
|
if buildconfig.substs.get('ENABLE_STRIP'):
|
|
self.skip_test = True
|
|
self.topsrcdir = buildconfig.topsrcdir
|
|
self.script_path = os.path.join(self.topsrcdir, 'toolkit',
|
|
'crashreporter', 'tools',
|
|
'symbolstore.py')
|
|
if target_platform() == 'WINNT':
|
|
if buildconfig.substs['MSVC_HAS_DIA_SDK']:
|
|
self.dump_syms = os.path.join(buildconfig.topobjdir,
|
|
'dist', 'host', 'bin',
|
|
'dump_syms.exe')
|
|
else:
|
|
self.dump_syms = os.path.join(self.topsrcdir,
|
|
'toolkit',
|
|
'crashreporter',
|
|
'tools',
|
|
'win32',
|
|
'dump_syms_vc{_MSC_VER}.exe'.format(**buildconfig.substs))
|
|
self.target_bin = os.path.join(buildconfig.topobjdir,
|
|
'dist', 'bin',
|
|
'firefox.exe')
|
|
else:
|
|
self.dump_syms = os.path.join(buildconfig.topobjdir,
|
|
'dist', 'host', 'bin',
|
|
'dump_syms')
|
|
self.target_bin = os.path.join(buildconfig.topobjdir,
|
|
'dist', 'bin', 'firefox')
|
|
|
|
|
|
def tearDown(self):
|
|
HelperMixin.tearDown(self)
|
|
|
|
def testSymbolstore(self):
|
|
if self.skip_test:
|
|
raise unittest.SkipTest('Skipping test in non-Firefox product')
|
|
dist_include_manifest = os.path.join(buildconfig.topobjdir,
|
|
'_build_manifests/install/dist_include')
|
|
dist_include = os.path.join(buildconfig.topobjdir, 'dist/include')
|
|
browser_app = os.path.join(buildconfig.topobjdir, 'browser/app')
|
|
output = subprocess.check_output([sys.executable,
|
|
self.script_path,
|
|
'--vcs-info',
|
|
'-s', self.topsrcdir,
|
|
'--install-manifest=%s,%s' % (dist_include_manifest,
|
|
dist_include),
|
|
self.dump_syms,
|
|
self.test_dir,
|
|
self.target_bin],
|
|
stderr=open(os.devnull, 'w'),
|
|
cwd=browser_app)
|
|
lines = filter(lambda x: x.strip(), output.splitlines())
|
|
self.assertEqual(1, len(lines),
|
|
'should have one filename in the output')
|
|
symbol_file = os.path.join(self.test_dir, lines[0])
|
|
self.assertTrue(os.path.isfile(symbol_file))
|
|
symlines = open(symbol_file, 'r').readlines()
|
|
file_lines = [l for l in symlines if l.startswith('FILE')]
|
|
def check_hg_path(lines, match):
|
|
match_lines = [l for l in file_lines if match in l]
|
|
self.assertTrue(len(match_lines) >= 1,
|
|
'should have a FILE line for ' + match)
|
|
# Skip this check for local git repositories.
|
|
if not os.path.isdir(mozpath.join(self.topsrcdir, '.hg')):
|
|
return
|
|
for line in match_lines:
|
|
filename = line.split(None, 2)[2]
|
|
self.assertEqual('hg:', filename[:3])
|
|
# Check that nsBrowserApp.cpp is listed as a FILE line, and that
|
|
# it was properly mapped to the source repo.
|
|
check_hg_path(file_lines, 'nsBrowserApp.cpp')
|
|
# Also check Assertions.h to verify that files from dist/include
|
|
# are properly mapped.
|
|
check_hg_path(file_lines, 'mfbt/Assertions.h')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
mozunit.main()
|