Skip to content

Commit 4c8b73e

Browse files
DanAlbertAndroid Build Coastguard Worker
authored and
Android Build Coastguard Worker
committed
Fix ndk-stack, remove test reliance on mocks.
ndk-stack was moved to being a zipapp, which changed the path of __file__ relative to the root of the NDK. This was covered by a test (97% line coverage, even), but the test was useless because it was only testing mocks, and the mocks weren't updated. The "system test" has been updated to test the real ndk-stack entry point in the NDK rather than importing the source and mocking dependency discovery. This unfortunately means that running the tests now depends on more or less the whole NDK, but that's true of everything else in the tests/ directory anyway. If that's ever a problem, we should split up the tests so that the test of the NDK itself and the tests of the ndk python package are kept separately. Bug: android/ndk#1751 Test: fixed the test, also tested manually (cherry picked from https://android-review.googlesource.com/q/commit:a54b719d892e8d19fe0ee9218762f0d9093c88f1) Merged-In: I77076d1c3fc8ac5a05a93dbe1ce3488874283143 Change-Id: I77076d1c3fc8ac5a05a93dbe1ce3488874283143
1 parent 28227ce commit 4c8b73e

File tree

4 files changed

+59
-54
lines changed

4 files changed

+59
-54
lines changed

docs/changelogs/Changelog-r26.md

+7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ directly, see the [build system maintainers guide].
2121

2222
[Issue 1751]: https://github.com/android/ndk/issues/1751
2323

24+
## r26b
25+
26+
* [Issue 1938]: Fixed ndk-stack to use the correct path for llvm-symbolizer and
27+
other tools.
28+
29+
[Issue 1938]: https://github.com/android/ndk/issues/1938
30+
2431
## Changes
2532

2633
* Updated LLVM to clang-r487747c, based on LLVM 17 development.

ndk/checkbuild.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,7 @@ def run(self) -> None:
845845
@register
846846
class Pytest(ndk.builds.LintModule):
847847
name = "pytest"
848+
deps = {"ndk-stack", "ndk-stack-shortcut"}
848849

849850
def run(self) -> None:
850851
if not shutil.which("pytest"):
@@ -1781,7 +1782,13 @@ class NdkStack(ndk.builds.PythonApplication):
17811782
notice = NDK_DIR / "NOTICE"
17821783
package = NDK_DIR / "ndkstack.py"
17831784
main = "ndkstack:main"
1784-
deps = {"ndk-stack-shortcut"}
1785+
deps = {
1786+
# PythonApplication depends on build/tools/ndk_bin_common.sh.
1787+
"ndk-build",
1788+
"ndk-stack-shortcut",
1789+
# PythonApplication depends on Python, which is bundled with Clang.
1790+
"toolchain",
1791+
}
17851792

17861793

17871794
@register

ndkstack.py

+13-4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import sys
2929
import tempfile
3030
import zipfile
31+
from pathlib import Path
3132

3233
EXE_SUFFIX = ".exe" if os.name == "nt" else ""
3334

@@ -57,14 +58,22 @@ def get_ndk_paths() -> tuple[str, str, str]:
5758
The platform name (eg linux-x86_64).
5859
"""
5960

61+
# ndk-stack is installed as a zipped Python application (created with zipapp). The
62+
# behavior of __file__ when Python runs a zip file doesn't appear to be documented,
63+
# but experimentally for this case it will be:
64+
#
65+
# $NDK/prebuilt/darwin-x86_64/bin/ndkstack.pyz/ndkstack.py
66+
#
6067
# ndk-stack is installed to $NDK/prebuilt/<platform>/bin, so from
6168
# `android-ndk-r18/prebuilt/linux-x86_64/bin/ndk-stack`...
6269
# ...get `android-ndk-r18/`:
63-
ndk_bin = os.path.dirname(os.path.realpath(__file__))
64-
ndk_root = os.path.abspath(os.path.join(ndk_bin, "../../.."))
70+
path_in_zipped_app = Path(__file__)
71+
zip_root = path_in_zipped_app.parent
72+
ndk_bin = zip_root.parent
73+
ndk_root = ndk_bin.parent.parent.parent
6574
# ...get `linux-x86_64`:
66-
ndk_host_tag = os.path.basename(os.path.abspath(os.path.join(ndk_bin, "../")))
67-
return (ndk_root, ndk_bin, ndk_host_tag)
75+
ndk_host_tag = ndk_bin.parent.name
76+
return (str(ndk_root), str(ndk_bin), str(ndk_host_tag))
6877

6978

7079
def find_llvm_symbolizer(ndk_root: str, ndk_bin: str, ndk_host_tag: str) -> str:

tests/pytest/ndkstack/test_systemtest.py

+31-49
Original file line numberDiff line numberDiff line change
@@ -16,77 +16,59 @@
1616
#
1717
"""System tests for ndk-stack.py"""
1818

19-
from __future__ import print_function
20-
2119
import os.path
20+
import subprocess
2221
import unittest
23-
from io import StringIO
24-
from unittest.mock import patch
2522

26-
import ndk.hosts
23+
import ndk.paths
2724
import ndk.toolchains
28-
import ndkstack
25+
from ndk.hosts import Host
2926

3027

3128
class SystemTests(unittest.TestCase):
3229
"""Complete system test of ndk-stack.py script."""
3330

34-
def setUp(self):
35-
default_host = ndk.hosts.get_default_host()
36-
clang_toolchain = ndk.toolchains.ClangToolchain(default_host)
37-
38-
# First try and use the normal functions, and if they fail, then
39-
# use hard-coded paths from the development locations.
40-
ndk_paths = ndkstack.get_ndk_paths()
41-
self.readelf = ndkstack.find_readelf(*ndk_paths)
42-
if not self.readelf:
43-
self.readelf = clang_toolchain.clang_tool("llvm-readelf")
44-
self.assertTrue(self.readelf)
45-
self.assertTrue(os.path.exists(self.readelf))
46-
47-
try:
48-
self.llvm_symbolizer = ndkstack.find_llvm_symbolizer(*ndk_paths)
49-
except OSError:
50-
self.llvm_symbolizer = str(clang_toolchain.clang_tool("llvm-symbolizer"))
51-
self.assertTrue(self.llvm_symbolizer)
52-
self.assertTrue(os.path.exists(self.llvm_symbolizer))
31+
def system_test(self, backtrace_file: str, expected_file: str) -> None:
32+
ndk_path = ndk.paths.get_install_path()
33+
self.assertTrue(
34+
ndk_path.exists(),
35+
f"{ndk_path} does not exist. Build the NDK before running this test.",
36+
)
5337

54-
@patch.object(ndkstack, "find_llvm_symbolizer")
55-
@patch.object(ndkstack, "find_readelf")
56-
def system_test(
57-
self, backtrace_file, expected_file, mock_readelf, mock_llvm_symbolizer
58-
):
59-
mock_readelf.return_value = self.readelf
60-
mock_llvm_symbolizer.return_value = self.llvm_symbolizer
38+
ndk_stack = ndk_path / "ndk-stack"
39+
if Host.current() is Host.Windows64:
40+
ndk_stack = ndk_stack.with_suffix(".bat")
6141

6242
symbol_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "files")
63-
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
64-
ndkstack.main(
65-
["-s", symbol_dir, "-i", os.path.join(symbol_dir, backtrace_file)]
66-
)
43+
proc = subprocess.run(
44+
[
45+
ndk_stack,
46+
"-s",
47+
symbol_dir,
48+
"-i",
49+
os.path.join(symbol_dir, backtrace_file),
50+
],
51+
check=True,
52+
capture_output=True,
53+
text=True,
54+
)
6755

6856
# Read the expected output.
6957
file_name = os.path.join(symbol_dir, expected_file)
7058
with open(file_name, "r", encoding="utf-8") as exp_file:
7159
expected = exp_file.read()
7260
expected = expected.replace("SYMBOL_DIR", symbol_dir)
7361
self.maxDiff = None
74-
self.assertEqual(expected, mock_stdout.getvalue())
62+
self.assertEqual(expected, proc.stdout)
7563

76-
def test_all_stacks(self):
77-
self.system_test( # pylint:disable=no-value-for-parameter
78-
"backtrace.txt", "expected.txt"
79-
)
64+
def test_all_stacks(self) -> None:
65+
self.system_test("backtrace.txt", "expected.txt")
8066

81-
def test_multiple_crashes(self):
82-
self.system_test( # pylint:disable=no-value-for-parameter
83-
"multiple.txt", "expected_multiple.txt"
84-
)
67+
def test_multiple_crashes(self) -> None:
68+
self.system_test("multiple.txt", "expected_multiple.txt")
8569

86-
def test_hwasan(self):
87-
self.system_test( # pylint:disable=no-value-for-parameter
88-
"hwasan.txt", "expected_hwasan.txt"
89-
)
70+
def test_hwasan(self) -> None:
71+
self.system_test("hwasan.txt", "expected_hwasan.txt")
9072

9173

9274
if __name__ == "__main__":

0 commit comments

Comments
 (0)