193 lines
6.7 KiB
Python
193 lines
6.7 KiB
Python
|
#!/usr/bin/env python3
|
||
|
# Copyright 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.
|
||
|
|
||
|
|
||
|
import argparse
|
||
|
import os
|
||
|
import shlex
|
||
|
import subprocess
|
||
|
import sys
|
||
|
|
||
|
|
||
|
PREBUILTS_DIR = os.path.dirname(__file__)
|
||
|
PROJECTS = {'acloud', 'aidegen', 'atest'}
|
||
|
ARCHS = {'linux-x86', 'darwin-x86'}
|
||
|
SMOKE_TEST = 'smoke_tests'
|
||
|
EXIT_TEST_PASS = 0
|
||
|
EXIT_TEST_FAIL = 1
|
||
|
EXIT_INVALID_BINS = 2
|
||
|
|
||
|
|
||
|
def _get_prebuilt_bins():
|
||
|
"""Get asuite prebuilt binaries.
|
||
|
|
||
|
Returns:
|
||
|
A set of prebuilt binaries.
|
||
|
"""
|
||
|
bins = {os.path.join(prj, arch, prj) for prj in PROJECTS for arch in ARCHS}
|
||
|
# atest becomes an entrypoint which invokes atest-py2 and atest-py3 accordingly.
|
||
|
bins.add('atest/linux-x86/atest-py2')
|
||
|
bins.add('atest/linux-x86/atest-py3')
|
||
|
return bins
|
||
|
|
||
|
|
||
|
def _get_prebuilt_dirs():
|
||
|
"""Get asuite prebuilt directories.
|
||
|
|
||
|
Returns:
|
||
|
A set of prebuilt paths of binaries.
|
||
|
"""
|
||
|
return {os.path.dirname(bin) for bin in _get_prebuilt_bins()}
|
||
|
|
||
|
|
||
|
def _get_smoke_tests_bins():
|
||
|
"""Get asuite smoke test scripts.
|
||
|
|
||
|
Returns:
|
||
|
A dict of project and smoke test script paths.
|
||
|
"""
|
||
|
return {prj: os.path.join(prj, SMOKE_TEST) for prj in PROJECTS}
|
||
|
|
||
|
|
||
|
def _is_executable(bin_path):
|
||
|
"""Check if the given file is executable.
|
||
|
|
||
|
Args:
|
||
|
bin_path: a string of a file path.
|
||
|
|
||
|
Returns:
|
||
|
True if it is executable, false otherwise.
|
||
|
"""
|
||
|
return os.access(bin_path, os.X_OK)
|
||
|
|
||
|
|
||
|
def check_uploaded_bins(preupload_files):
|
||
|
"""This method validates the uploaded files.
|
||
|
|
||
|
If the uploaded file is in prebuilt_bins, ensure:
|
||
|
- it is executable.
|
||
|
- only one at a time.
|
||
|
If the uploaded file is a smoke_test script, ensure:
|
||
|
- it is executable.
|
||
|
If the uploaded file is placed in prebuilt_dirs, ensure:
|
||
|
- it is not executable.
|
||
|
(It is to ensure PATH is not contaminated.
|
||
|
e.g. atest/linux-x86/atest-dev will override $OUT/bin/atest-dev, or
|
||
|
atest/linux-x86/rm does fraud/harmful things.)
|
||
|
|
||
|
Args:
|
||
|
preupload_files: A list of preuploaded files.
|
||
|
|
||
|
Returns:
|
||
|
True is the above criteria are all fulfilled, otherwise None.
|
||
|
"""
|
||
|
prebuilt_bins = _get_prebuilt_bins()
|
||
|
prebuilt_dirs = _get_prebuilt_dirs()
|
||
|
smoke_tests_bins = _get_smoke_tests_bins().values()
|
||
|
# Store valid executables.
|
||
|
target_bins = set()
|
||
|
# Unexpected executable files which may cause issues(they are in $PATH).
|
||
|
illegal_bins = set()
|
||
|
# Store prebuilts or smoke test script that are inexecutable.
|
||
|
insufficient_perm_bins = set()
|
||
|
for f in preupload_files:
|
||
|
# Ensure target_bins are executable.
|
||
|
if f in prebuilt_bins:
|
||
|
if _is_executable(f):
|
||
|
target_bins.add(f)
|
||
|
else:
|
||
|
insufficient_perm_bins.add(f)
|
||
|
# Ensure smoke_tests scripts are executable.
|
||
|
elif f in smoke_tests_bins and not _is_executable(f):
|
||
|
insufficient_perm_bins.add(f)
|
||
|
# Avoid fraud commands in $PATH. e.g. atest/linux-x86/rm.
|
||
|
# must not be executable.
|
||
|
elif os.path.dirname(f) in prebuilt_dirs and _is_executable(f):
|
||
|
illegal_bins.add(f)
|
||
|
if len(target_bins) > 1:
|
||
|
print('\nYou\'re uploading multiple binaries: %s'
|
||
|
% ' '.join(target_bins))
|
||
|
print('\nPlease upload one prebuilt at a time.')
|
||
|
return False
|
||
|
if insufficient_perm_bins:
|
||
|
print('\nInsufficient permission found: %s'
|
||
|
% ' '.join(insufficient_perm_bins))
|
||
|
print('\nPlease run:\n\tchmod 0755 %s\nand try again.'
|
||
|
% ' '.join(insufficient_perm_bins))
|
||
|
return False
|
||
|
if illegal_bins:
|
||
|
illegal_dirs = {os.path.dirname(bin) for bin in illegal_bins}
|
||
|
print('\nIt is forbidden to upload executable file: %s'
|
||
|
% '\n - %s\n' % '\n - '.join(illegal_bins))
|
||
|
print('Because they are in the project paths: %s'
|
||
|
% '\n - %s\n' % '\n - '.join(illegal_dirs))
|
||
|
print('Please remove the binaries or make the files non-executable.')
|
||
|
return False
|
||
|
return True
|
||
|
|
||
|
|
||
|
def run_smoke_tests_pass(files_to_check):
|
||
|
"""Run smoke tests.
|
||
|
|
||
|
Args:
|
||
|
files_to_check: A list of preuploaded files to check.
|
||
|
|
||
|
Returns:
|
||
|
True when test passed or no need to test.
|
||
|
False when test failed.
|
||
|
"""
|
||
|
for target in files_to_check:
|
||
|
if target in _get_prebuilt_bins():
|
||
|
project = target.split(os.path.sep)[0]
|
||
|
test_file = _get_smoke_tests_bins().get(project)
|
||
|
if os.path.exists(test_file):
|
||
|
try:
|
||
|
subprocess.check_output(test_file, encoding='utf-8',
|
||
|
stderr=subprocess.STDOUT)
|
||
|
except subprocess.CalledProcessError as error:
|
||
|
print('Smoke tests failed at:\n\n%s' % error.output)
|
||
|
return False
|
||
|
except OSError as oserror:
|
||
|
print('%s: Missing the header of the script.' % oserror)
|
||
|
print('Please define shebang like:\n')
|
||
|
print('#!/usr/bin/env bash\nor')
|
||
|
print('#!/usr/bin/env python3\n')
|
||
|
return False
|
||
|
return True
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
parser = argparse.ArgumentParser()
|
||
|
parser.add_argument('--skip-smoke-test', '-s', action="store_true",
|
||
|
help='Disable smoke testing.')
|
||
|
parser.add_argument('preupload_files', nargs='*', help='Files to be uploaded.')
|
||
|
args = parser.parse_args()
|
||
|
files_to_check = args.preupload_files
|
||
|
# Pre-process files_to_check(run directly by users.)
|
||
|
if not files_to_check:
|
||
|
# Only consider added(A), renamed(R) and modified(M) files.
|
||
|
cmd = "git status --short | egrep ^[ARM] | awk '{print $NF}'"
|
||
|
preupload_files = subprocess.check_output(cmd, shell=True,
|
||
|
encoding='utf-8').splitlines()
|
||
|
if preupload_files:
|
||
|
print('validating: %s' % preupload_files)
|
||
|
files_to_check = preupload_files
|
||
|
# Validating uploaded files and run smoke test script(run by repohook).
|
||
|
if not check_uploaded_bins(files_to_check):
|
||
|
sys.exit(EXIT_INVALID_BINS)
|
||
|
if not args.skip_smoke_test and not run_smoke_tests_pass(files_to_check):
|
||
|
sys.exit(EXIT_TEST_FAIL)
|
||
|
sys.exit(EXIT_TEST_PASS)
|