blob: 71701597a664d0fd251a9cb4c225a0cd3de995fc [file]
"""Build step class for stripping OTA test binaries."""
# pylint triggers false positives in _setup/_teardown_mocks
# pylint: disable=method-hidden
from __future__ import absolute_import
import logging
import os
import sys
from slave import base_step
_UNIT_TEST_ARCHIVE_MODULE = 'unit_test.unit_test_archive_lib'
_UNIT_TEST_HELPER_MODULE = 'unit_test.unit_test_helper'
# If a file is larger than this, the strip tool will fail to recognize the file.
_MAX_32BIT_FILESIZE = 2**32
_MAX_32BIT_FILESIZE_STR = '4GB'
_STRIPPED_EXT = '.stripped'
def _InjectPythonPath(path):
if path not in sys.path:
sys.path.append(path)
class OtaStripBinariesStep(base_step.BaseStep):
"""Build step class for stripping OTA test binaries."""
def __init__(self, **kwargs):
"""Creates a OtaStripBinariesStep instance.
Args:
**kwargs: Any additional args to pass to BaseStep.
"""
base_step.BaseStep.__init__(self, name='strip binaries', **kwargs)
# Use the unit_test_helper modules to be consistent with Catabuilder.
self._unit_test_helper = None
self._unit_test_archive_lib = None
self._const = None
# Save list of files that need to be cleaned up at the end.
# Can't cleanup stripped binaries until after done archiving them.
self._stripped_binaries = []
@property
def const(self):
if not self._const:
self._const = self.unit_test_helper.GetUnitTestConst(None)
return self._const
@property
def gcs_archive_dir(self):
output_dir = os.path.join(self.get_gcs_dir(), 'artifacts', 'tests')
if not os.path.exists(output_dir):
os.makedirs(output_dir)
return output_dir
@property
def unit_test_helper(self):
"""Use the unit_test_helper module to be consistent with Catabuilder."""
if not self._unit_test_helper:
_InjectPythonPath(self.get_project_path('test'))
self._unit_test_helper = __import__(
_UNIT_TEST_HELPER_MODULE, fromlist=[''])
return self._unit_test_helper
@property
def unit_test_archive_lib(self):
"""Use unit_test_archive_lib module to be consistent with Catabuilder."""
if not self._unit_test_archive_lib:
_InjectPythonPath(self.get_project_path('test'))
self._unit_test_archive_lib = __import__(
_UNIT_TEST_ARCHIVE_MODULE, fromlist=[''])
return self._unit_test_archive_lib
def _get_out_dir(self):
chromium_src_dir = self.get_project_path('chromium/src')
return self.unit_test_helper.GetOutDir(chromium_src_dir)
def _get_binaries_to_strip(self, out_dir):
return [
binary for binary in
self.unit_test_helper.GetListOfBinariesToBeStripped(out_dir)
if binary.strip()
]
def _clear_test_binaries(self, test_binary_paths):
for binary in test_binary_paths:
stripped_binary = binary + _STRIPPED_EXT
if os.path.exists(stripped_binary):
try:
os.remove(stripped_binary)
except OSError:
continue
def _get_test_files_to_archive(self, out_dir, stripped_binary_paths):
"""Gets the set of test binaries & dependency files to archive."""
stripped_binary_names = set(
[os.path.basename(path) for path in stripped_binary_paths])
# Get a flattened deduped list of all binaries & dependencies to tar.
tar_files = set()
deps_dict = self.unit_test_archive_lib.GetAllTestDependenciesDict(
self.const, out_dir)
for test_deps in deps_dict.values():
for dependency in test_deps:
# Use the stripped version of test binaries where available.
if os.path.basename(
dependency) in stripped_binary_names and os.path.exists(
os.path.join(self.const.chrome_srcroot,
dependency + _STRIPPED_EXT)):
tar_files.add(dependency + _STRIPPED_EXT)
else:
tar_files.add(dependency)
return tar_files
def _strip_binary(self, file_path, output_binary):
return self.unit_test_helper.StripBinary(
file_path, output_file=output_binary)
def _strip_error_to_comment(self, binary, stderr):
"""Convert errors from the strip tool into comments for gerrit."""
if not stderr:
return ''
comment = stderr.replace(os.path.abspath(os.getcwd()), '')
binary_size = os.path.getsize(binary)
if binary_size > _MAX_32BIT_FILESIZE:
logging.warning('%s=%s bytes > %s', binary, binary_size,
_MAX_32BIT_FILESIZE_STR)
comment += ('{binary} ({size:,}bytes) is larger than {max_size}.'
' Too large to strip. (b/33284348)'.format(
binary=os.path.basename(binary),
size=binary_size,
max_size=_MAX_32BIT_FILESIZE_STR))
return comment
def _strip_binaries(self, test_binaries):
"""Strip the test binaries built for the most recent ota."""
failure_messages = []
for binary in test_binaries:
# Try to strip the binary.
output_binary = binary + _STRIPPED_EXT
try:
self._strip_binary(binary, output_binary)
except IOError as e:
# If stripping failed, report the error.
failure_messages.append(self._strip_error_to_comment(binary, str(e)))
return failure_messages
def _archive_binaries(self, files_to_archive):
self.unit_test_archive_lib.ZipWithRelativeFileList(
self.const.chrome_srcroot,
os.path.join(self.gcs_archive_dir, 'test_deps.tar.gz'),
files_to_archive,
# Remove .stripped from all filenames within the tar.
transform=r's|{}$||'.format(_STRIPPED_EXT))
def run(self):
"""Run the strip tool over the unittest binaries and archive them to GCS."""
try:
out_dir = self._get_out_dir()
except ValueError as error:
# |out_dir| may not exist if a previous build step failed. However, we
# still want this step to be marked as failing in case the out_dir does
# actually exist somewhere and this step isn't finding it correctly.
self.add_review({'message': error.message})
return False
test_binary_paths = self._get_binaries_to_strip(out_dir)
# Clear in case recipe aborted in a previous run before able to cleanup
self._clear_test_binaries(test_binary_paths)
failure_messages = self._strip_binaries(test_binary_paths)
if failure_messages:
self.add_review({'message': '\n'.join(failure_messages)})
return False
# Archive stripped test binaries into a tar file in gcs_archive_dir
# so that they'll be uploaded to GCS. Catatester will use the tar file
# for running these tests on-device in the lab.
files_to_archive = self._get_test_files_to_archive(out_dir,
test_binary_paths)
self._archive_binaries(files_to_archive)
self._clear_test_binaries(test_binary_paths)
return True