blob: cb2e68a2d4aaadc68bf0f3851e119c71287e7453 [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright (C) 2019 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Unittests for ndk-stack.py"""
from __future__ import print_function
import os.path
import sys
import textwrap
import unittest
from unittest import mock
from unittest.mock import patch
try:
# Python 2
from cStringIO import StringIO
except ModuleNotFoundError: # pylint:disable=undefined-variable
# Python 3
from io import StringIO
sys.path.insert(0, '../..')
ndk_stack = __import__('ndk-stack')
@patch('os.path.exists')
class PathTests(unittest.TestCase):
"""Tests of find_llvm_symbolizer() and find_readelf()."""
def setUp(self):
self.ndk_paths = ('/ndk_fake', '/ndk_fake/bin', 'linux-x86_64')
exe_suffix = '.EXE' if os.name == 'nt' else ''
self.llvm_symbolizer = 'llvm-symbolizer' + exe_suffix
self.readelf = 'llvm-readelf' + exe_suffix
def test_find_llvm_symbolizer_in_prebuilt(self, mock_exists):
expected_path = os.path.join('/ndk_fake', 'toolchains', 'llvm',
'prebuilt', 'linux-x86_64', 'bin',
self.llvm_symbolizer)
mock_exists.return_value = True
self.assertEqual(expected_path,
ndk_stack.find_llvm_symbolizer(*self.ndk_paths))
mock_exists.assert_called_once_with(expected_path)
def test_find_llvm_symbolizer_in_standalone_toolchain(self, mock_exists):
prebuilt_path = os.path.join('/ndk_fake', 'toolchains', 'llvm',
'prebuilt', 'linux-x86_64', 'bin',
self.llvm_symbolizer)
expected_path = os.path.join('/ndk_fake', 'bin', self.llvm_symbolizer)
mock_exists.side_effect = [False, True]
self.assertEqual(expected_path,
ndk_stack.find_llvm_symbolizer(*self.ndk_paths))
mock_exists.assert_has_calls(
[mock.call(prebuilt_path),
mock.call(expected_path)])
def test_llvm_symbolizer_not_found(self, mock_exists):
mock_exists.return_value = False
with self.assertRaises(OSError) as cm:
ndk_stack.find_llvm_symbolizer(*self.ndk_paths)
self.assertEqual('Unable to find llvm-symbolizer', str(cm.exception))
def test_find_readelf_in_prebuilt(self, mock_exists):
expected_path = os.path.join('/ndk_fake', 'toolchains', 'llvm',
'prebuilt', 'linux-x86_64', 'bin',
self.readelf)
mock_exists.return_value = True
self.assertEqual(expected_path,
ndk_stack.find_readelf(*self.ndk_paths))
mock_exists.assert_called_once_with(expected_path)
def test_find_readelf_in_prebuilt_arm(self, mock_exists):
expected_path = os.path.join('/ndk_fake', 'toolchains', 'llvm',
'prebuilt', 'linux-arm', 'bin',
self.readelf)
mock_exists.return_value = True
self.assertEqual(
expected_path,
ndk_stack.find_readelf('/ndk_fake', '/ndk_fake/bin', 'linux-arm'))
mock_exists.assert_called_once_with(expected_path)
def test_find_readelf_in_standalone_toolchain(self, mock_exists):
mock_exists.reset_mock()
expected_path = os.path.join('/ndk_fake', 'bin', self.readelf)
mock_exists.side_effect = [False, True]
os.path.exists = lambda path, exp=expected_path: path == exp
self.assertEqual(expected_path,
ndk_stack.find_readelf(*self.ndk_paths))
def test_readelf_not_found(self, mock_exists):
mock_exists.return_value = False
self.assertFalse(ndk_stack.find_readelf(*self.ndk_paths))
class FrameTests(unittest.TestCase):
"""Test parsing of backtrace lines."""
def test_line_with_map_name(self):
line = ' #14 pc 00001000 /fake/libfake.so'
frame_info = ndk_stack.FrameInfo.from_line(line)
self.assertTrue(frame_info)
self.assertEqual('#14', frame_info.num)
self.assertEqual('00001000', frame_info.pc)
self.assertEqual('/fake/libfake.so', frame_info.tail)
self.assertEqual('/fake/libfake.so', frame_info.elf_file)
self.assertFalse(frame_info.offset)
self.assertFalse(frame_info.container_file)
self.assertFalse(frame_info.build_id)
def test_line_with_function(self):
line = ' #08 pc 00001040 /fake/libfake.so (func())'
frame_info = ndk_stack.FrameInfo.from_line(line)
self.assertTrue(frame_info)
self.assertEqual('#08', frame_info.num)
self.assertEqual('00001040', frame_info.pc)
self.assertEqual('/fake/libfake.so (func())', frame_info.tail)
self.assertEqual('/fake/libfake.so', frame_info.elf_file)
self.assertFalse(frame_info.offset)
self.assertFalse(frame_info.container_file)
self.assertFalse(frame_info.build_id)
def test_line_with_offset(self):
line = ' #04 pc 00002050 /fake/libfake.so (offset 0x2000)'
frame_info = ndk_stack.FrameInfo.from_line(line)
self.assertTrue(frame_info)
self.assertEqual('#04', frame_info.num)
self.assertEqual('00002050', frame_info.pc)
self.assertEqual('/fake/libfake.so (offset 0x2000)', frame_info.tail)
self.assertEqual('/fake/libfake.so', frame_info.elf_file)
self.assertEqual(0x2000, frame_info.offset)
self.assertFalse(frame_info.container_file)
self.assertFalse(frame_info.build_id)
def test_line_with_build_id(self):
line = ' #03 pc 00002050 /fake/libfake.so (BuildId: d1d420a58366bf29f1312ec826f16564)'
frame_info = ndk_stack.FrameInfo.from_line(line)
self.assertTrue(frame_info)
self.assertEqual('#03', frame_info.num)
self.assertEqual('00002050', frame_info.pc)
self.assertEqual(
'/fake/libfake.so (BuildId: d1d420a58366bf29f1312ec826f16564)',
frame_info.tail)
self.assertEqual('/fake/libfake.so', frame_info.elf_file)
self.assertFalse(frame_info.offset)
self.assertFalse(frame_info.container_file)
self.assertEqual('d1d420a58366bf29f1312ec826f16564',
frame_info.build_id)
def test_line_with_container_file(self):
line = ' #10 pc 00003050 /fake/fake.apk!libc.so'
frame_info = ndk_stack.FrameInfo.from_line(line)
self.assertTrue(frame_info)
self.assertEqual('#10', frame_info.num)
self.assertEqual('00003050', frame_info.pc)
self.assertEqual('/fake/fake.apk!libc.so', frame_info.tail)
self.assertEqual('libc.so', frame_info.elf_file)
self.assertFalse(frame_info.offset)
self.assertEqual('/fake/fake.apk', frame_info.container_file)
self.assertFalse(frame_info.build_id)
def test_line_with_container_and_elf_equal(self):
line = ' #12 pc 00004050 /fake/libc.so!lib/libc.so'
frame_info = ndk_stack.FrameInfo.from_line(line)
self.assertTrue(frame_info)
self.assertEqual('#12', frame_info.num)
self.assertEqual('00004050', frame_info.pc)
self.assertEqual('/fake/libc.so!lib/libc.so', frame_info.tail)
self.assertEqual('/fake/libc.so', frame_info.elf_file)
self.assertFalse(frame_info.offset)
self.assertFalse(frame_info.container_file)
self.assertFalse(frame_info.build_id)
def test_line_everything(self):
line = (' #07 pc 00823fc /fake/fake.apk!libc.so (__start_thread+64) '
'(offset 0x1000) (BuildId: 6a0c10d19d5bf39a5a78fa514371dab3)')
frame_info = ndk_stack.FrameInfo.from_line(line)
self.assertTrue(frame_info)
self.assertEqual('#07', frame_info.num)
self.assertEqual('00823fc', frame_info.pc)
self.assertEqual(
'/fake/fake.apk!libc.so (__start_thread+64) '
'(offset 0x1000) (BuildId: 6a0c10d19d5bf39a5a78fa514371dab3)',
frame_info.tail)
self.assertEqual('libc.so', frame_info.elf_file)
self.assertEqual(0x1000, frame_info.offset)
self.assertEqual('/fake/fake.apk', frame_info.container_file)
self.assertEqual('6a0c10d19d5bf39a5a78fa514371dab3',
frame_info.build_id)
@patch.object(ndk_stack, 'get_build_id')
@patch('os.path.exists')
class VerifyElfFileTests(unittest.TestCase):
"""Tests of verify_elf_file()."""
def create_frame_info(self):
line = ' #03 pc 00002050 /fake/libfake.so'
frame_info = ndk_stack.FrameInfo.from_line(line)
self.assertTrue(frame_info)
return frame_info
def test_elf_file_does_not_exist(self, mock_exists, _):
mock_exists.return_value = False
frame_info = self.create_frame_info()
self.assertFalse(
frame_info.verify_elf_file(None, '/fake/libfake.so', 'libfake.so'))
self.assertFalse(
frame_info.verify_elf_file('llvm-readelf', '/fake/libfake.so',
'libfake.so'))
def test_elf_file_build_id_matches(self, mock_exists, mock_get_build_id):
mock_exists.return_value = True
frame_info = self.create_frame_info()
frame_info.build_id = 'MOCKED_BUILD_ID'
self.assertTrue(
frame_info.verify_elf_file(None, '/mocked/libfake.so',
'libfake.so'))
mock_get_build_id.assert_not_called()
mock_get_build_id.return_value = 'MOCKED_BUILD_ID'
self.assertTrue(
frame_info.verify_elf_file('llvm-readelf', '/mocked/libfake.so',
'libfake.so'))
mock_get_build_id.assert_called_once_with('llvm-readelf',
'/mocked/libfake.so')
def test_elf_file_build_id_does_not_match(self, mock_exists,
mock_get_build_id):
mock_exists.return_value = True
mock_get_build_id.return_value = 'MOCKED_BUILD_ID'
frame_info = self.create_frame_info()
frame_info.build_id = 'DIFFERENT_BUILD_ID'
with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
self.assertTrue(
frame_info.verify_elf_file(None, '/mocked/libfake.so',
'none.so'))
self.assertFalse(
frame_info.verify_elf_file('llvm-readelf',
'/mocked/libfake.so', 'display.so'))
output = textwrap.dedent("""\
WARNING: Mismatched build id for display.so
WARNING: Expected DIFFERENT_BUILD_ID
WARNING: Found MOCKED_BUILD_ID
""")
self.assertEqual(output, mock_stdout.getvalue())
class GetZipInfoFromOffsetTests(unittest.TestCase):
"""Tests of get_zip_info_from_offset()."""
def setUp(self):
self.mock_zip = mock.MagicMock()
self.mock_zip.filename = '/fake/zip.apk'
self.mock_zip.infolist.return_value = []
def test_file_does_not_exist(self):
with self.assertRaises(IOError):
_ = ndk_stack.get_zip_info_from_offset(self.mock_zip, 0x1000)
@patch('os.stat')
def test_offset_ge_file_size(self, mock_stat):
mock_stat.return_value.st_size = 0x1000
self.assertFalse(ndk_stack.get_zip_info_from_offset(self.mock_zip, 0x1000))
self.assertFalse(ndk_stack.get_zip_info_from_offset(self.mock_zip, 0x1100))
@patch('os.stat')
def test_empty_infolist(self, mock_stat):
mock_stat.return_value.st_size = 0x1000
self.assertFalse(ndk_stack.get_zip_info_from_offset(self.mock_zip, 0x900))
@patch('os.stat')
def test_zip_info_single_element(self, mock_stat):
mock_stat.return_value.st_size = 0x2000
mock_zip_info = mock.MagicMock()
mock_zip_info.header_offset = 0x100
self.mock_zip.infolist.return_value = [mock_zip_info]
self.assertFalse(ndk_stack.get_zip_info_from_offset(self.mock_zip, 0x50))
self.assertFalse(ndk_stack.get_zip_info_from_offset(self.mock_zip, 0x2000))
zip_info = ndk_stack.get_zip_info_from_offset(self.mock_zip, 0x200)
self.assertEqual(0x100, zip_info.header_offset)
@patch('os.stat')
def test_zip_info_checks(self, mock_stat):
mock_stat.return_value.st_size = 0x2000
mock_zip_info1 = mock.MagicMock()
mock_zip_info1.header_offset = 0x100
mock_zip_info2 = mock.MagicMock()
mock_zip_info2.header_offset = 0x1000
self.mock_zip.infolist.return_value = [mock_zip_info1, mock_zip_info2]
self.assertFalse(ndk_stack.get_zip_info_from_offset(self.mock_zip, 0x50))
zip_info = ndk_stack.get_zip_info_from_offset(self.mock_zip, 0x200)
self.assertEqual(0x100, zip_info.header_offset)
zip_info = ndk_stack.get_zip_info_from_offset(self.mock_zip, 0x100)
self.assertEqual(0x100, zip_info.header_offset)
zip_info = ndk_stack.get_zip_info_from_offset(self.mock_zip, 0x1000)
self.assertEqual(0x1000, zip_info.header_offset)
class GetElfFileTests(unittest.TestCase):
"""Tests of FrameInfo.get_elf_file()."""
def setUp(self):
self.mock_zipfile = mock.MagicMock()
self.mock_zipfile.extract.return_value = '/fake_tmp/libtest.so'
self.mock_zipfile.__enter__.return_value = self.mock_zipfile
self.mock_tmp = mock.MagicMock()
self.mock_tmp.get_directory.return_value = '/fake_tmp'
def create_frame_info(self, tail):
line = ' #03 pc 00002050 ' + tail
frame_info = ndk_stack.FrameInfo.from_line(line)
self.assertTrue(frame_info)
frame_info.verify_elf_file = mock.MagicMock()
return frame_info
def test_file_only(self):
frame_info = self.create_frame_info('/fake/libfake.so')
frame_info.verify_elf_file.return_value = True
self.assertEqual(
'/fake_dir/symbols/libfake.so',
frame_info.get_elf_file('/fake_dir/symbols', None, self.mock_tmp))
frame_info.verify_elf_file.reset_mock()
frame_info.verify_elf_file.return_value = False
self.assertFalse(
frame_info.get_elf_file('/fake_dir/symbols', None, self.mock_tmp))
self.assertEqual('/fake/libfake.so', frame_info.tail)
def test_container_set_elf_in_symbol_dir(self):
frame_info = self.create_frame_info('/fake/fake.apk!libtest.so')
frame_info.verify_elf_file.return_value = True
self.assertEqual(
'/fake_dir/symbols/libtest.so',
frame_info.get_elf_file('/fake_dir/symbols', None, self.mock_tmp))
self.assertEqual('/fake/fake.apk!libtest.so', frame_info.tail)
def test_container_set_elf_not_in_symbol_dir_apk_does_not_exist(self):
frame_info = self.create_frame_info('/fake/fake.apk!libtest.so')
frame_info.verify_elf_file.return_value = False
with self.assertRaises(IOError):
frame_info.get_elf_file('/fake_dir/symbols', None, self.mock_tmp)
self.assertEqual('/fake/fake.apk!libtest.so', frame_info.tail)
@patch.object(ndk_stack, 'get_zip_info_from_offset')
@patch('zipfile.ZipFile')
def test_container_set_elf_not_in_apk(self, _,
mock_get_zip_info):
mock_get_zip_info.return_value = None
frame_info = self.create_frame_info('/fake/fake.apk!libtest.so')
frame_info.verify_elf_file.return_value = False
self.assertFalse(
frame_info.get_elf_file('/fake_dir/symbols', None, self.mock_tmp))
self.assertEqual('/fake/fake.apk!libtest.so', frame_info.tail)
@patch.object(ndk_stack, 'get_zip_info_from_offset')
@patch('zipfile.ZipFile')
def test_container_set_elf_in_apk(self, mock_zipclass, mock_get_zip_info):
mock_zipclass.return_value = self.mock_zipfile
mock_get_zip_info.return_value.filename = 'libtest.so'
frame_info = self.create_frame_info('/fake/fake.apk!libtest.so')
frame_info.verify_elf_file.side_effect = [False, True]
self.assertEqual(
'/fake_tmp/libtest.so',
frame_info.get_elf_file('/fake_dir/symbols', None, self.mock_tmp))
self.assertEqual('/fake/fake.apk!libtest.so', frame_info.tail)
@patch.object(ndk_stack, 'get_zip_info_from_offset')
@patch('zipfile.ZipFile')
def test_container_set_elf_in_apk_verify_fails(self, mock_zipclass,
mock_get_zip_info):
mock_zipclass.return_value = self.mock_zipfile
mock_get_zip_info.return_value.filename = 'libtest.so'
frame_info = self.create_frame_info('/fake/fake.apk!libtest.so')
frame_info.verify_elf_file.side_effect = [False, False]
self.assertFalse(
frame_info.get_elf_file('/fake_dir/symbols', None, self.mock_tmp))
self.assertEqual('/fake/fake.apk!libtest.so', frame_info.tail)
def test_in_apk_file_does_not_exist(self):
frame_info = self.create_frame_info('/fake/fake.apk')
frame_info.verify_elf_file.return_value = False
with self.assertRaises(IOError):
frame_info.get_elf_file('/fake_dir/symbols', None, self.mock_tmp)
self.assertEqual('/fake/fake.apk', frame_info.tail)
@patch.object(ndk_stack, 'get_zip_info_from_offset')
@patch('zipfile.ZipFile')
def test_in_apk_elf_not_in_apk(self, _, mock_get_zip_info):
mock_get_zip_info.return_value = None
frame_info = self.create_frame_info('/fake/fake.apk')
self.assertFalse(
frame_info.get_elf_file('/fake_dir/symbols', None, self.mock_tmp))
self.assertEqual('/fake/fake.apk', frame_info.tail)
@patch.object(ndk_stack, 'get_zip_info_from_offset')
@patch('zipfile.ZipFile')
def test_in_apk_elf_in_symbol_dir(self, mock_zipclass, mock_get_zip_info):
mock_zipclass.return_value = self.mock_zipfile
mock_get_zip_info.return_value.filename = 'libtest.so'
frame_info = self.create_frame_info('/fake/fake.apk')
frame_info.verify_elf_file.return_value = True
self.assertEqual(
'/fake_dir/symbols/libtest.so',
frame_info.get_elf_file('/fake_dir/symbols', None, self.mock_tmp))
self.assertEqual('/fake/fake.apk!libtest.so', frame_info.tail)
@patch.object(ndk_stack, 'get_zip_info_from_offset')
@patch('zipfile.ZipFile')
def test_in_apk_elf_in_apk(self, mock_zipclass, mock_get_zip_info):
mock_zipclass.return_value = self.mock_zipfile
mock_get_zip_info.return_value.filename = 'libtest.so'
frame_info = self.create_frame_info('/fake/fake.apk')
frame_info.verify_elf_file.side_effect = [False, True]
self.assertEqual(
'/fake_tmp/libtest.so',
frame_info.get_elf_file('/fake_dir/symbols', None, self.mock_tmp))
self.assertEqual('/fake/fake.apk!libtest.so', frame_info.tail)
@patch.object(ndk_stack, 'get_zip_info_from_offset')
@patch('zipfile.ZipFile')
def test_in_apk_elf_in_apk_verify_fails(self, mock_zipclass,
mock_get_zip_info):
mock_zipclass.return_value = self.mock_zipfile
mock_get_zip_info.return_value.filename = 'libtest.so'
frame_info = self.create_frame_info('/fake/fake.apk')
frame_info.verify_elf_file.side_effect = [False, False]
self.assertFalse(
frame_info.get_elf_file('/fake_dir/symbols', None, self.mock_tmp))
self.assertEqual('/fake/fake.apk!libtest.so', frame_info.tail)
if __name__ == "__main__":
unittest.main()