RESTRICT AUTOMERGE Do not test the ELF objects not built for Android am: 9f34c6128a
Original change: https://android-review.googlesource.com/c/platform/test/vts-testcase/vndk/+/2923412
Change-Id: I37405f8ef5b0bbea4bdb5f9169e072f658053f41
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index d7b26f9..ccae16d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -16,8 +16,9 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-python_library_host {
+python_library {
name: "vts_vndk_utils",
+ host_supported: true,
pkg_path: "vts/testcases/vndk",
srcs: [
"utils.py",
@@ -42,6 +43,14 @@
}
}
+// TODO(b/243602514): Python data should not be put in testcases dir.
+python_library_host {
+ name: "vts_vndk_abi_dump_lib",
+ data: [
+ ":vts_vndk_abi_dump_zip",
+ ],
+}
+
python_test_host {
name: "vts_vndk_abi_test",
defaults: ["vts_vndk_default"],
@@ -49,8 +58,8 @@
srcs: [
"abi/vts_vndk_abi_test.py",
],
- data: [
- ":vts_vndk_abi_dump_zip",
+ libs: [
+ "vts_vndk_abi_dump_lib",
],
test_suites: [
"vts",
@@ -61,8 +70,12 @@
},
}
-python_test_host {
+python_test {
name: "vts_vndk_dependency_test",
+ // vts_vndk_dependency_test.xml refers to the file name. It needs to be
+ // different from the directory name so that the test runner can find a
+ // unique path.
+ stem: "vts_vndk_dependency_test_bin",
defaults: ["vts_vndk_default"],
main: "dependency/vts_vndk_dependency_test.py",
srcs: [
diff --git a/abi/vts_vndk_abi_test.py b/abi/vts_vndk_abi_test.py
index 386e812..a73a23f 100644
--- a/abi/vts_vndk_abi_test.py
+++ b/abi/vts_vndk_abi_test.py
@@ -307,6 +307,9 @@
Args:
bitness: 32 or 64, the bitness of the tested libraries.
"""
+ if not vndk_utils.IsVndkRequired(self._dut):
+ logging.info("Skip the test as the device does not require VNDK.")
+ return
self.assertTrue(self._dut.IsRoot(), "This test requires adb root.")
primary_abi = self._dut.GetCpuAbiList()[0]
binder_bitness = self._dut.GetBinderBitness()
diff --git a/dependency/vts_vndk_dependency_test.py b/dependency/vts_vndk_dependency_test.py
index f11838c..c7aeebc 100644
--- a/dependency/vts_vndk_dependency_test.py
+++ b/dependency/vts_vndk_dependency_test.py
@@ -18,11 +18,8 @@
import collections
import logging
import os
-import posixpath as target_path_module
import re
-import shutil
import sys
-import tempfile
import unittest
from vts.testcases.vndk import utils
@@ -36,8 +33,6 @@
Attributes:
_dut: The AndroidDevice under test.
- _temp_dir: The temporary directory to which the odm and vendor
- partitions are copied.
_vndk_version: The VNDK version of the device.
_ll_ndk: Set of strings. The names of low-level NDK libraries in
/system/lib[64].
@@ -45,6 +40,7 @@
expected to be in /vendor/lib[64].
_vndk: Set of strings. The names of VNDK-core libraries.
_vndk_sp: Set of strings. The names of VNDK-SP libraries.
+ _VENDOR_DIRS: The directories of vendor partitions.
_SP_HAL_LINK_PATHS: Format strings of same-process HAL's default link
paths.
_VENDOR_LINK_PATHS: Format strings of vendor processes' default link
@@ -53,11 +49,9 @@
permitted link paths.
_VENDOR_APP_DIRS: The app directories in vendor partitions.
"""
- _TARGET_DIR_SEP = "/"
- _TARGET_ROOT_DIR = "/"
- _TARGET_ODM_DIR = "/odm"
- _TARGET_VENDOR_DIR = "/vendor"
-
+ _VENDOR_DIRS = [
+ "/odm", "/vendor"
+ ]
_SP_HAL_LINK_PATHS = [
"/odm/{LIB}/egl", "/odm/{LIB}/hw", "/odm/{LIB}",
"/vendor/{LIB}/egl", "/vendor/{LIB}/hw", "/vendor/{LIB}"
@@ -93,8 +87,8 @@
def __init__(self, target_path, bitness, deps, runpaths,
custom_link_paths):
self.target_path = target_path
- self.name = target_path_module.basename(target_path)
- self.target_dir = target_path_module.dirname(target_path)
+ self.name = os.path.basename(target_path)
+ self.target_dir = os.path.dirname(target_path)
self.bitness = bitness
self.deps = deps
# Format runpaths
@@ -109,20 +103,10 @@
self.custom_link_paths = custom_link_paths
def setUp(self):
- """Initializes device, temporary directory, and VNDK lists."""
- serial_number = os.environ.get("ANDROID_SERIAL")
- self.assertTrue(serial_number, "$ANDROID_SERIAL is empty.")
- self._dut = utils.AndroidDevice(serial_number)
+ """Initializes VNDK lists."""
+ self._dut = utils.AndroidDevice()
self.assertTrue(self._dut.IsRoot(), "This test requires adb root.")
- self._temp_dir = tempfile.mkdtemp()
- for target_dir in (self._TARGET_ODM_DIR, self._TARGET_VENDOR_DIR):
- if self._dut.IsDirectory(target_dir):
- logging.info("adb pull %s %s", target_dir, self._temp_dir)
- self._dut.AdbPull(target_dir, self._temp_dir)
- else:
- logging.info("Skip adb pull %s", target_dir)
-
self._vndk_version = self._dut.GetVndkVersion()
vndk_lists = vndk_data.LoadVndkLibraryListsFromResources(
self._vndk_version,
@@ -134,22 +118,21 @@
sp_hal_strings = vndk_lists[0]
self._sp_hal = [re.compile(x) for x in sp_hal_strings]
- self._ll_ndk = vndk_lists[1]
- if vndk_utils.IsVndkInstalledInVendor(self._dut):
- (self._vndk, self._vndk_sp) = ([], [])
+ if vndk_utils.IsVndkRequired(self._dut):
+ self._ll_ndk = vndk_lists[1]
+ if vndk_utils.IsVndkInstalledInVendor(self._dut):
+ (self._vndk, self._vndk_sp) = ([], [])
+ else:
+ (self._vndk, self._vndk_sp) = vndk_lists[2:]
else:
- (self._vndk, self._vndk_sp) = vndk_lists[2:]
+ self._ll_ndk = self._dut.GetLlndkList()
+ (self._vndk, self._vndk_sp) = ([], [])
logging.debug("LL_NDK: %s", self._ll_ndk)
logging.debug("SP_HAL: %s", sp_hal_strings)
logging.debug("VNDK: %s", self._vndk)
logging.debug("VNDK_SP: %s", self._vndk_sp)
- def tearDown(self):
- """Deletes the temporary directory."""
- logging.info("Delete %s", self._temp_dir)
- shutil.rmtree(self._temp_dir, ignore_errors=True)
-
def _IsElfObjectForAp(self, elf, target_path, abi_list):
"""Checks whether an ELF object is for application processor.
@@ -219,26 +202,24 @@
return True
@staticmethod
- def _IterateFiles(host_dir):
- """Iterates files in a host directory.
+ def _IterateFiles(target_dir):
+ """Iterates files in a directory.
Args:
- host_dir: The host directory.
+ target_dir: The directory.
Yields:
The file paths under the directory.
"""
- for root_dir, dir_names, file_names in os.walk(host_dir):
+ for root_dir, dir_names, file_names in os.walk(target_dir):
for file_name in file_names:
yield os.path.join(root_dir, file_name)
- def _LoadElfObjects(self, host_dir, target_dir, abi_list,
- elf_error_handler):
- """Scans a host directory recursively and loads all ELF files in it.
+ def _LoadElfObjects(self, target_dir, abi_list, elf_error_handler):
+ """Scans a directory recursively and loads all ELF files in it.
Args:
- host_dir: The host directory to scan.
- target_dir: The path from which host_dir is copied.
+ target_dir: The directory to scan.
abi_list: A list of strings, the ABIs of the ELF files to load.
elf_error_handler: A function that takes 2 arguments
(target_path, exception). It is called when
@@ -248,12 +229,9 @@
List of ElfObject.
"""
objs = []
- for full_path in self._IterateFiles(host_dir):
- rel_path = os.path.relpath(full_path, host_dir)
- target_path = target_path_module.join(
- target_dir, *rel_path.split(os.path.sep))
+ for target_path in self._IterateFiles(target_dir):
try:
- elf = elf_parser.ElfParser(full_path)
+ elf = elf_parser.ElfParser(target_path)
except elf_parser.ElfError:
logging.debug("%s is not an ELF file", target_path)
continue
@@ -279,10 +257,9 @@
# b/123216664 App libraries depend on those in the same directory.
custom_link_paths = []
- if any(target_path.startswith(app_dir + self._TARGET_DIR_SEP) for
+ if any(target_path.startswith(app_dir + os.path.sep) for
app_dir in self._VENDOR_APP_DIRS):
- custom_link_paths.append(
- target_path_module.dirname(target_path))
+ custom_link_paths.append(os.path.dirname(target_path))
objs.append(self.ElfObject(target_path, elf.bitness, deps,
runpaths, custom_link_paths))
@@ -304,8 +281,7 @@
namespace = collections.defaultdict(dict)
for obj in objs:
if (obj.bitness == bitness and
- any(obj.target_path.startswith(link_path +
- self._TARGET_DIR_SEP)
+ any(obj.target_path.startswith(link_path + os.path.sep)
for link_path in link_paths)):
namespace[obj.target_dir][obj.name] = obj
return namespace
@@ -392,7 +368,7 @@
if vndk_utils.IsVndkInstalledInVendor(self._dut):
vndk_in_vendor = [
- target_path_module.basename(lib_path) for lib_path in
+ os.path.basename(lib_path) for lib_path in
self._dut.FindFiles(
vndk_utils.GetVndkDirectory(bitness, self._vndk_version),
"*", "!", "-type", "d")]
@@ -467,9 +443,11 @@
"""Tests vendor libraries/executables and SP-HAL dependencies."""
read_errors = []
abi_list = self._dut.GetCpuAbiList()
- objs = self._LoadElfObjects(
- self._temp_dir, self._TARGET_ROOT_DIR, abi_list,
- lambda p, e: read_errors.append((p, str(e))))
+ objs = []
+ for target_dir in self._VENDOR_DIRS:
+ objs += self._LoadElfObjects(
+ target_dir, abi_list,
+ lambda p, e: read_errors.append((p, str(e))))
dep_errors = self._TestElfDependency(32, objs)
if self._dut.GetCpuAbiList(64):
@@ -498,9 +476,6 @@
if __name__ == "__main__":
- # The logs are written to stdout so that TradeFed test runner can parse the
- # results from stderr.
+ # Write logs to stdout and results to stderr.
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
- # Setting verbosity is required to generate output that the TradeFed test
- # runner can parse.
unittest.main(verbosity=3)
diff --git a/dependency/vts_vndk_dependency_test.xml b/dependency/vts_vndk_dependency_test.xml
index 0a15678..7c8868e 100644
--- a/dependency/vts_vndk_dependency_test.xml
+++ b/dependency/vts_vndk_dependency_test.xml
@@ -15,9 +15,16 @@
-->
<configuration description="Config for vts_vndk_dependency_test">
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
- <test class="com.android.tradefed.testtype.python.PythonBinaryHostTest">
- <option name="par-file-name" value="vts_vndk_dependency_test" />
- <option name="test-timeout" value="10m" />
+
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer" >
+ <option name="push-file" key="vts_vndk_dependency_test_bin" value="/data/local/tmp/vts_vndk_dependency_test" />
+ <option name="abort-on-push-failure" value="true" />
+ <option name="cleanup" value="true" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.binary.ExecutableTargetTest">
+ <option name="binary" value="/data/local/tmp/vts_vndk_dependency_test" />
+ <option name="per-binary-timeout" value="3m" />
</test>
</configuration>
diff --git a/files/vts_vndk_files_test.py b/files/vts_vndk_files_test.py
index 24f2d82..9c1510f 100644
--- a/files/vts_vndk_files_test.py
+++ b/files/vts_vndk_files_test.py
@@ -101,28 +101,24 @@
"The above libraries are not %s." %
", ".join(vndk_list_names))
- def _TestNotInVndkDirecotory(self, vndk_dir, vndk_list_names, except_libs):
- """Verifies that VNDK directory doesn't contain specific files.
+ def _TestNoLlndkInDirectory(self, lib_dir):
+ """Verifies that the vendor directory doesn't contain LLNDK libraries.
Args:
- vndk_dir, The path to the VNDK directory on device.
- vndk_list_names: A list of strings, the categories of the VNDK
- libraries that should not be in the directory.
- except_libs: A set of strings, the file names of the libraries that
- are exceptions to this test.
+ lib_dir: The path to the directory on device.
"""
- vndk_lists = vndk_data.LoadVndkLibraryListsFromResources(
- self._vndk_version, *vndk_list_names)
- self.assertTrue(vndk_lists, "Cannot load VNDK library lists.")
- vndk_set = set().union(*vndk_lists)
- vndk_set.difference_update(except_libs)
- logging.debug("vndk set: %s", vndk_set)
- unexpected = [x for x in self._ListFiles(vndk_dir) if
- target_path_module.basename(x) in vndk_set]
+ if vndk_utils.IsVndkRequired(self._dut):
+ llndk_list = vndk_data.LoadVndkLibraryListsFromResources(
+ self._vndk_version, vndk_data.LL_NDK)[0]
+ else:
+ llndk_list = self._dut.GetLlndkList()
+ llndk_set = set(llndk_list).difference(self._LL_NDK_COLLIDING_NAMES)
+ logging.debug("llndk set: %s", llndk_set)
+ unexpected = [x for x in self._ListFiles(lib_dir) if
+ target_path_module.basename(x) in llndk_set]
if unexpected:
self._Fail(unexpected,
- "%s must not contain %s libraries." %
- (vndk_dir, ", ",join(vndk_list_names)))
+ lib_dir + " must not contain LLNDK libraries.")
def _TestVndkCoreDirectory(self, bitness):
"""Verifies that VNDK directory doesn't contain extra files."""
@@ -130,6 +126,9 @@
logging.info("Skip the test as VNDK runtime is not enforced on "
"the device.")
return
+ if not vndk_utils.IsVndkRequired(self._dut):
+ logging.info("Skip the test as the device does not require VNDK.")
+ return
if vndk_utils.IsVndkInstalledInVendor(self._dut):
logging.info("Skip the test as VNDK %s should be installed in "
"vendor partition.", self._vndk_version)
@@ -153,10 +152,8 @@
def _TestNoLlndkInVendor(self, bitness):
"""Verifies that vendor partition has no LL-NDK libraries."""
- self._TestNotInVndkDirecotory(
- vndk_utils.FormatVndkPath(self._TARGET_VENDOR_LIB, bitness),
- (vndk_data.LL_NDK,),
- self._LL_NDK_COLLIDING_NAMES)
+ self._TestNoLlndkInDirectory(
+ vndk_utils.FormatVndkPath(self._TARGET_VENDOR_LIB, bitness))
def testNoLlndkInVendor32(self):
"""Runs _TestNoLlndkInVendor for 32-bit libraries."""
@@ -172,10 +169,8 @@
def _TestNoLlndkInOdm(self, bitness):
"""Verifies that odm partition has no LL-NDK libraries."""
- self._TestNotInVndkDirecotory(
- vndk_utils.FormatVndkPath(self._TARGET_ODM_LIB, bitness),
- (vndk_data.LL_NDK,),
- self._LL_NDK_COLLIDING_NAMES)
+ self._TestNoLlndkInDirectory(
+ vndk_utils.FormatVndkPath(self._TARGET_ODM_LIB, bitness))
def testNoLlndkInOdm32(self):
"""Runs _TestNoLlndkInOdm for 32-bit libraries."""
diff --git a/golden/extract_lsdump.py b/golden/extract_lsdump.py
index 8849d48..fe70be4 100755
--- a/golden/extract_lsdump.py
+++ b/golden/extract_lsdump.py
@@ -284,10 +284,9 @@
# the large file group.
if zipfile.is_zipfile(args.input_path):
with zipfile.ZipFile(args.input_path, mode='r') as input_zip:
- # The zip will be added to a Python package. It is not necessary
- # to reduce the file size.
- with zipfile.ZipFile(args.output_path, mode='w',
- compression=zipfile.ZIP_STORED) as output_zip:
+ with zipfile.ZipFile(
+ args.output_path, mode='w',
+ compression=zipfile.ZIP_DEFLATED) as output_zip:
_ParseLsdumpZipFile(input_zip, output_zip)
sys.exit(0)
diff --git a/golden/vndk_data.py b/golden/vndk_data.py
index a35c1c0..bb1a8d7 100644
--- a/golden/vndk_data.py
+++ b/golden/vndk_data.py
@@ -79,8 +79,8 @@
self.zip_file = None
def __enter__(self):
- self._resource = resources.open_binary(_RESOURCE_PACKAGE,
- _ABI_DUMP_ZIP_NAME)
+ self._resource = resources.files(_RESOURCE_PACKAGE).joinpath(
+ _ABI_DUMP_ZIP_NAME).open("rb")
self.zip_file = zipfile.ZipFile(self._resource, "r")
return self
@@ -137,32 +137,51 @@
return dump_paths
-def _LoadVndkLibraryListsFile(vndk_lists, tags, vndk_lib_list_file):
+def _LoadVndkLibraryListsFile(vndk_lists, tags, vndk_lib_list_file,
+ change_history_file=None):
"""Load VNDK libraries from the file to the specified tuple.
Args:
vndk_lists: The output tuple of lists containing library names.
tags: Strings, the tags of the libraries to find.
vndk_lib_list_file: The file object containing the VNDK library list.
+ change_history_file: The file object containing the VNDK list change
+ history. It adds the vndk files that are removed.
"""
+ def ReadTagAndFile(line):
+ # Ignore comments.
+ if line.startswith('#'):
+ return None, None
+
+ # Split columns.
+ cells = line.split(': ', 1)
+ if len(cells) < 2:
+ return None, None
+ return cells[0].strip(), cells[1].strip()
lib_sets = collections.defaultdict(set)
# Load VNDK tags from the list.
for line in vndk_lib_list_file:
- # Ignore comments.
- if line.startswith('#'):
+ tag, lib_name = ReadTagAndFile(line)
+ if not tag:
continue
-
- # Split columns.
- cells = line.split(': ', 1)
- if len(cells) < 2:
- continue
- tag = cells[0]
- lib_name = cells[1].strip()
-
lib_sets[tag].add(lib_name)
+ if change_history_file:
+ for line in change_history_file:
+ tag, lib_name = ReadTagAndFile(line)
+ if not tag:
+ continue
+
+ # In the history file, tag has '+' prefix if the file is added and
+ # '-' prefix if removed.
+ # To relax the test, include the removed files to the list.
+ if tag[0] != '-':
+ continue
+ tag = tag[1:]
+ lib_sets[tag].add(lib_name)
+
# Compute VNDK-core-private and VNDK-SP-private.
private = lib_sets.get('VNDK-private', set())
@@ -202,20 +221,31 @@
version_str = (version if re.match("\\d+", version) and int(version) <= 34
else "current")
vndk_lib_list_name = version_str + ".txt"
+ vndk_lib_list = resources.files(_RESOURCE_PACKAGE).joinpath(
+ vndk_lib_list_name)
+ vndk_lib_list_history_name = version_str + "_history.txt"
+ vndk_lib_list_history = resources.files(_RESOURCE_PACKAGE).joinpath(
+ vndk_lib_list_history_name)
vndk_lib_extra_list_name = "vndk-lib-extra-list-" + version_str + ".txt"
+ vndk_lib_extra_list = resources.files(_RESOURCE_PACKAGE).joinpath(
+ vndk_lib_extra_list_name)
- if not resources.is_resource(_RESOURCE_PACKAGE, vndk_lib_list_name):
+ if not vndk_lib_list.is_file():
logging.warning("Cannot load %s.", vndk_lib_list_name)
return None
- if not resources.is_resource(_RESOURCE_PACKAGE, vndk_lib_extra_list_name):
+ if not vndk_lib_extra_list.is_file():
logging.warning("Cannot load %s.", vndk_lib_extra_list_name)
return None
vndk_lists = tuple([] for x in tags)
- with resources.open_text(_RESOURCE_PACKAGE, vndk_lib_list_name) as f:
- _LoadVndkLibraryListsFile(vndk_lists, tags, f)
- with resources.open_text(_RESOURCE_PACKAGE, vndk_lib_extra_list_name) as f:
+ with vndk_lib_list.open("r") as f:
+ if vndk_lib_list_history.is_file():
+ with vndk_lib_list_history.open("r") as history:
+ _LoadVndkLibraryListsFile(vndk_lists, tags, f, history)
+ else:
+ _LoadVndkLibraryListsFile(vndk_lists, tags, f)
+ with vndk_lib_extra_list.open("r") as f:
_LoadVndkLibraryListsFile(vndk_lists, tags, f)
return vndk_lists
diff --git a/utils.py b/utils.py
index 8bf00f3..36814ab 100644
--- a/utils.py
+++ b/utils.py
@@ -15,32 +15,50 @@
# limitations under the License.
#
-# TODO(b/147454897): Keep the logic in sync with
-# test/vts/utils/python/controllers/android_device.py until
-# it is removed.
import gzip
import logging
import os
+import shlex
import subprocess
import tempfile
-class AndroidDevice(object):
- """This class controls the device via adb commands."""
- def __init__(self, serial_number):
+class AndroidDevice(object):
+ """This class controls the device via adb or shell commands."""
+
+ def __init__(self, serial_number=None):
+ """Initialize the serial number.
+
+ A non-empty serial number indicates that this process runs on a host
+ and controls the devices via adb. An empty serial number indicates that
+ this process runs on an Android device and executes shell commands.
+
+ Args:
+ serial_number: A string or None.
+ """
self._serial_number = serial_number
+ @property
+ def _adb_mode(self):
+ """Returns whether this objects executes adb commands."""
+ return bool(self._serial_number)
+
def AdbPull(self, src, dst):
+ if not self._adb_mode:
+ raise NotImplementedError("Cannot execute `adb pull` on device.")
cmd = ["adb", "-s", self._serial_number, "pull", src, dst]
env = os.environ.copy()
if "ADB_COMPRESSION" not in env:
env["ADB_COMPRESSION"] = "0"
- subprocess.check_call(cmd, shell=False, env=env, stdin=subprocess.PIPE,
- stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ subprocess.run(cmd, shell=False, env=env, check=True,
+ stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
def Execute(self, *args):
"""Executes a command.
+ The caller should escape special characters.
+
Args:
args: Strings, the arguments.
@@ -48,9 +66,14 @@
Stdout as a string, stderr as a string, and return code as an
integer.
"""
- cmd = ["adb", "-s", self._serial_number, "shell"]
- cmd.extend(args)
- proc = subprocess.Popen(cmd, shell=False, stdin=subprocess.PIPE,
+ if self._adb_mode:
+ cmd = ["adb", "-s", self._serial_number, "shell"]
+ cmd.extend(args)
+ else:
+ cmd = " ".join(args)
+
+ proc = subprocess.Popen(cmd, shell=not self._adb_mode,
+ stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = proc.communicate()
# Compatible with python2 and python3
@@ -192,6 +215,19 @@
else:
return 64
+ def GetLlndkList(self):
+ """Loads the list of LLNDK library names from the device.
+
+ Returns:
+ A list of strings, the library names including ".so".
+ """
+ out, err, return_code = self.Execute("cat",
+ "/system/etc/llndk.libraries.txt")
+ if err.strip() or return_code != 0:
+ raise IOError("`cat /system/etc/llndk.libraries.txt` "
+ f"stdout: {out}\nstderr: {err}\n")
+ return out.split()
+
def IsRoot(self):
"""Returns whether adb has root privilege on the device."""
out, err, return_code = self.Execute("id")
@@ -245,8 +281,7 @@
if '"' in name_pattern or "'" in name_pattern:
raise ValueError("File name pattern contains quotes.")
out, err, return_code = self.Execute("find", path, "-name",
- "'" + name_pattern + "'",
- *options)
+ f"'{name_pattern}'", *options)
if return_code != 0 or err.strip():
raise IOError("`find %s -name '%s' %s` stdout: %s\nstderr: %s" %
(path, name_pattern, " ".join(options), out, err))