blob: e76426a7548799a3d85a53233586eceacb939eac [file] [log] [blame]
Jim Tang7be96242019-04-12 12:53:41 +08001#!/usr/bin/env python3
2# Copyright 2019, The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16
17import argparse
18import os
19import shlex
20import subprocess
21import sys
22
23
24PREBUILTS_DIR = os.path.dirname(__file__)
25PROJECTS = {'acloud', 'aidegen', 'atest'}
26ARCHS = {'linux-x86', 'darwin-x86'}
27SMOKE_TEST = 'smoke_tests'
28EXIT_TEST_PASS = 0
29EXIT_TEST_FAIL = 1
30EXIT_INVALID_BINS = 2
31
32
33def _get_prebuilt_bins():
34 """Get asuite prebuilt binaries.
35
36 Returns:
37 A set of prebuilt binaries.
38 """
Jim Tang66124a32020-04-15 16:31:11 +080039 bins = {os.path.join(prj, arch, prj) for prj in PROJECTS for arch in ARCHS}
40 # atest becomes an entrypoint which invokes atest-py2 and atest-py3 accordingly.
41 bins.add('atest/linux-x86/atest-py2')
42 bins.add('atest/linux-x86/atest-py3')
43 return bins
Jim Tang7be96242019-04-12 12:53:41 +080044
45
46def _get_prebuilt_dirs():
47 """Get asuite prebuilt directories.
48
49 Returns:
50 A set of prebuilt paths of binaries.
51 """
52 return {os.path.dirname(bin) for bin in _get_prebuilt_bins()}
53
54
55def _get_smoke_tests_bins():
56 """Get asuite smoke test scripts.
57
58 Returns:
59 A dict of project and smoke test script paths.
60 """
61 return {prj: os.path.join(prj, SMOKE_TEST) for prj in PROJECTS}
62
63
64def _is_executable(bin_path):
65 """Check if the given file is executable.
66
67 Args:
68 bin_path: a string of a file path.
69
70 Returns:
71 True if it is executable, false otherwise.
72 """
73 return os.access(bin_path, os.X_OK)
74
75
76def check_uploaded_bins(preupload_files):
77 """This method validates the uploaded files.
78
79 If the uploaded file is in prebuilt_bins, ensure:
80 - it is executable.
81 - only one at a time.
82 If the uploaded file is a smoke_test script, ensure:
83 - it is executable.
84 If the uploaded file is placed in prebuilt_dirs, ensure:
85 - it is not executable.
86 (It is to ensure PATH is not contaminated.
87 e.g. atest/linux-x86/atest-dev will override $OUT/bin/atest-dev, or
88 atest/linux-x86/rm does fraud/harmful things.)
89
90 Args:
91 preupload_files: A list of preuploaded files.
92
93 Returns:
94 True is the above criteria are all fulfilled, otherwise None.
95 """
96 prebuilt_bins = _get_prebuilt_bins()
97 prebuilt_dirs = _get_prebuilt_dirs()
98 smoke_tests_bins = _get_smoke_tests_bins().values()
99 # Store valid executables.
100 target_bins = set()
101 # Unexpected executable files which may cause issues(they are in $PATH).
102 illegal_bins = set()
103 # Store prebuilts or smoke test script that are inexecutable.
104 insufficient_perm_bins = set()
105 for f in preupload_files:
106 # Ensure target_bins are executable.
107 if f in prebuilt_bins:
108 if _is_executable(f):
109 target_bins.add(f)
110 else:
111 insufficient_perm_bins.add(f)
112 # Ensure smoke_tests scripts are executable.
113 elif f in smoke_tests_bins and not _is_executable(f):
114 insufficient_perm_bins.add(f)
115 # Avoid fraud commands in $PATH. e.g. atest/linux-x86/rm.
116 # must not be executable.
117 elif os.path.dirname(f) in prebuilt_dirs and _is_executable(f):
118 illegal_bins.add(f)
119 if len(target_bins) > 1:
120 print('\nYou\'re uploading multiple binaries: %s'
121 % ' '.join(target_bins))
122 print('\nPlease upload one prebuilt at a time.')
123 return False
124 if insufficient_perm_bins:
125 print('\nInsufficient permission found: %s'
126 % ' '.join(insufficient_perm_bins))
127 print('\nPlease run:\n\tchmod 0755 %s\nand try again.'
128 % ' '.join(insufficient_perm_bins))
129 return False
130 if illegal_bins:
131 illegal_dirs = {os.path.dirname(bin) for bin in illegal_bins}
132 print('\nIt is forbidden to upload executable file: %s'
133 % '\n - %s\n' % '\n - '.join(illegal_bins))
134 print('Because they are in the project paths: %s'
135 % '\n - %s\n' % '\n - '.join(illegal_dirs))
136 print('Please remove the binaries or make the files non-executable.')
137 return False
138 return True
139
140
141def run_smoke_tests_pass(files_to_check):
142 """Run smoke tests.
143
144 Args:
145 files_to_check: A list of preuploaded files to check.
146
147 Returns:
148 True when test passed or no need to test.
149 False when test failed.
150 """
151 for target in files_to_check:
152 if target in _get_prebuilt_bins():
Jim Tang66124a32020-04-15 16:31:11 +0800153 project = target.split(os.path.sep)[0]
Jim Tang7be96242019-04-12 12:53:41 +0800154 test_file = _get_smoke_tests_bins().get(project)
155 if os.path.exists(test_file):
156 try:
157 subprocess.check_output(test_file, encoding='utf-8',
158 stderr=subprocess.STDOUT)
159 except subprocess.CalledProcessError as error:
160 print('Smoke tests failed at:\n\n%s' % error.output)
161 return False
162 except OSError as oserror:
163 print('%s: Missing the header of the script.' % oserror)
164 print('Please define shebang like:\n')
165 print('#!/usr/bin/env bash\nor')
166 print('#!/usr/bin/env python3\n')
167 return False
168 return True
169
170
171if __name__ == '__main__':
172 parser = argparse.ArgumentParser()
173 parser.add_argument('--skip-smoke-test', '-s', action="store_true",
174 help='Disable smoke testing.')
175 parser.add_argument('preupload_files', nargs='*', help='Files to be uploaded.')
176 args = parser.parse_args()
177 files_to_check = args.preupload_files
178 # Pre-process files_to_check(run directly by users.)
179 if not files_to_check:
180 # Only consider added(A), renamed(R) and modified(M) files.
181 cmd = "git status --short | egrep ^[ARM] | awk '{print $NF}'"
182 preupload_files = subprocess.check_output(cmd, shell=True,
183 encoding='utf-8').splitlines()
184 if preupload_files:
185 print('validating: %s' % preupload_files)
186 files_to_check = preupload_files
187 # Validating uploaded files and run smoke test script(run by repohook).
188 if not check_uploaded_bins(files_to_check):
189 sys.exit(EXIT_INVALID_BINS)
190 if not args.skip_smoke_test and not run_smoke_tests_pass(files_to_check):
191 sys.exit(EXIT_TEST_FAIL)
192 sys.exit(EXIT_TEST_PASS)