blob: 879534fb0a979e3d284c10201c8921537d3882d6 [file] [log] [blame]
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +00001#!/usr/bin/python3
2import argparse
3import os
4import sys
5import subprocess
6import traceback
7import yaml
8
Chase Qi30290532018-11-20 18:54:43 +08009run_pycodestyle = False
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000010try:
Chase Qi30290532018-11-20 18:54:43 +080011 import pycodestyle
12 run_pycodestyle = True
Chase Qif70267b2019-03-13 12:23:37 +080013except ImportError as e:
14 print(e)
15 print("Install pycodestyle: pip3 install pycodestyle")
16 sys.exit(1)
17
18try:
19 import magic
20except ImportError as e:
21 print(e)
22 print("Install python-magic: pip3 install python-magic")
23 sys.exit(1)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000024
25
26def print_stderr(message):
27 sys.stderr.write(message)
28 sys.stderr.write("\n")
29
30
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +000031def publish_result(result_message_list, args):
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000032 result_message = '\n'.join(result_message_list)
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +000033 try:
34 f = open(args.result_file, 'a')
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +000035 f.write(result_message)
Milosz Wasilewskia85631a2020-03-02 11:21:29 +000036 f.write("\n")
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +000037 f.close()
38 except IOError as e:
Chase Qi9a5f4cf2019-03-25 11:41:55 +080039 print(e)
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +000040 print_stderr("Cannot write to result file: %s" % args.result_file)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000041 print_stderr(result_message)
42
43
Dan Rue8ec540c2018-01-25 14:38:12 -060044def detect_abi():
45 # Retrieve the current canonical abi from
46 # automated/lib/sh-test-lib:detect_abi
47 return subprocess.check_output(
48 ". automated/lib/sh-test-lib && detect_abi && echo $abi",
49 shell=True).decode('utf-8').strip()
50
51
Chase Qi30290532018-11-20 18:54:43 +080052def pycodestyle_check(filepath, args):
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000053 _fmt = "%(row)d:%(col)d: %(code)s %(text)s"
54 options = {
Chase Qi30290532018-11-20 18:54:43 +080055 'ignore': args.pycodestyle_ignore,
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000056 "show_source": True}
Chase Qi30290532018-11-20 18:54:43 +080057 pycodestyle_checker = pycodestyle.StyleGuide(options)
58 fchecker = pycodestyle_checker.checker_class(
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000059 filepath,
Chase Qi30290532018-11-20 18:54:43 +080060 options=pycodestyle_checker.options)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000061 fchecker.check_all()
62 if fchecker.report.file_errors > 0:
63 result_message_list = []
Chase Qi30290532018-11-20 18:54:43 +080064 result_message_list.append("* PYCODESTYLE: [FAILED]: " + filepath)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000065 fchecker.report.print_statistics()
66 for line_number, offset, code, text, doc in fchecker.report._deferred_print:
67 result_message_list.append(
68 _fmt % {
69 'path': filepath,
70 'row': fchecker.report.line_offset + line_number,
71 'col': offset + 1,
72 'code': code, 'text': text,
73 })
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +000074 publish_result(result_message_list, args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000075 return 1
76 else:
Chase Qi30290532018-11-20 18:54:43 +080077 message = "* PYCODESTYLE: [PASSED]: " + filepath
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000078 print_stderr(message)
79 return 0
80
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +000081
Dan Rue8ec540c2018-01-25 14:38:12 -060082def validate_yaml_contents(filepath, args):
Chase Qi827203c2019-03-07 16:19:15 +080083 def validate_testdef_yaml(y, args):
Milosz Wasilewskic69b4092017-10-20 14:04:52 +010084 result_message_list = []
Milosz Wasilewskic69b4092017-10-20 14:04:52 +010085 if 'metadata' not in y.keys():
86 result_message_list.append("* METADATA [FAILED]: " + filepath)
87 result_message_list.append("\tmetadata section missing")
88 publish_result(result_message_list, args)
89 exit(1)
90 metadata_dict = y['metadata']
91 mandatory_keys = set([
92 'name',
93 'format',
94 'description',
95 'maintainer',
96 'os',
97 'devices'])
98 if not mandatory_keys.issubset(set(metadata_dict.keys())):
99 result_message_list.append("* METADATA [FAILED]: " + filepath)
100 result_message_list.append("\tmandatory keys missing: %s" %
101 mandatory_keys.difference(set(metadata_dict.keys())))
102 result_message_list.append("\tactual keys present: %s" %
103 metadata_dict.keys())
104 publish_result(result_message_list, args)
105 return 1
106 for key in mandatory_keys:
107 if len(metadata_dict[key]) == 0:
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000108 result_message_list.append("* METADATA [FAILED]: " + filepath)
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100109 result_message_list.append("\t%s has no content" % key)
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000110 publish_result(result_message_list, args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000111 return 1
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100112 result_message_list.append("* METADATA [PASSED]: " + filepath)
113 publish_result(result_message_list, args)
Dan Rue8ec540c2018-01-25 14:38:12 -0600114 return 0
115
116 def validate_skipgen_yaml(filepath, args):
117 abi = detect_abi()
118 # Run skipgen on skipgen yaml file to check for output and errors
119 skips = subprocess.check_output(
120 "automated/bin/{}/skipgen {}".format(abi, filepath),
121 shell=True).decode('utf-8').strip()
122 if len(skips.split('\n')) < 1:
123 publish_result(["* SKIPGEN [FAILED]: " + filepath + " - No skips found"], args)
124 return 1
125 publish_result(["* SKIPGEN [PASSED]: " + filepath], args)
126 return 0
127
128 filecontent = None
129 try:
130 with open(filepath, "r") as f:
131 filecontent = f.read()
132 except FileNotFoundError:
133 publish_result(["* YAMLVALIDCONTENTS [PASSED]: " + filepath + " - deleted"], args)
134 return 0
Milosz Wasilewski1097c992020-03-02 11:38:02 +0000135 y = yaml.load(filecontent, Loader=yaml.FullLoader)
Chase Qi827203c2019-03-07 16:19:15 +0800136 if 'run' in y.keys():
137 # test definition yaml file
138 return validate_testdef_yaml(y, args)
Dan Rue8ec540c2018-01-25 14:38:12 -0600139 elif 'skiplist' in y.keys():
140 # skipgen yaml file
141 return validate_skipgen_yaml(filepath, args)
142 else:
Chase Qi827203c2019-03-07 16:19:15 +0800143 publish_result(["* YAMLVALIDCONTENTS [SKIPPED]: " + filepath + " - Unknown yaml type detected"], args)
144 return 0
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000145
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +0000146
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000147def validate_yaml(filename, args):
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100148 filecontent = None
149 try:
150 with open(filename, "r") as f:
151 filecontent = f.read()
152 except FileNotFoundError:
153 publish_result(["* YAMLVALID [PASSED]: " + filename + " - deleted"], args)
154 return 0
155 try:
Milosz Wasilewski1097c992020-03-02 11:38:02 +0000156 yaml.load(filecontent, Loader=yaml.FullLoader)
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100157 message = "* YAMLVALID: [PASSED]: " + filename
158 print_stderr(message)
Chase Qi30290532018-11-20 18:54:43 +0800159 except yaml.YAMLError:
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100160 message = "* YAMLVALID: [FAILED]: " + filename
161 result_message_list = []
162 result_message_list.append(message)
163 result_message_list.append("\n\n")
164 exc_type, exc_value, exc_traceback = sys.exc_info()
165 for line in traceback.format_exception_only(exc_type, exc_value):
166 result_message_list.append(' ' + line)
167 publish_result(result_message_list, args)
168 return 1
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000169 return 0
170
171
172def validate_shell(filename, ignore_options):
173 ignore_string = ""
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000174 if args.shellcheck_ignore is not None:
Chase Qied017432018-12-14 16:56:20 +0800175 # Exclude types of warnings in the following format:
176 # -e CODE1,CODE2..
177 ignore_string = "-e %s" % ",".join(args.shellcheck_ignore)
Milosz Wasilewskiab7645a2016-11-07 10:45:34 +0000178 if len(ignore_string) < 4: # contains only "-e "
179 ignore_string = ""
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000180 cmd = 'shellcheck %s' % ignore_string
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000181 return validate_external(cmd, filename, "SHELLCHECK", args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000182
183
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000184def validate_php(filename, args):
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000185 cmd = 'php -l'
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000186 return validate_external(cmd, filename, "PHPLINT", args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000187
188
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000189def validate_external(cmd, filename, prefix, args):
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000190 final_cmd = "%s %s 2>&1" % (cmd, filename)
191 status, output = subprocess.getstatusoutput(final_cmd)
192 if status == 0:
193 message = '* %s: [PASSED]: %s' % (prefix, filename)
194 print_stderr(message)
Milosz Wasilewskia85631a2020-03-02 11:21:29 +0000195 publish_result([message], args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000196 else:
197 result_message_list = []
198 result_message_list.append('* %s: [FAILED]: %s' % (prefix, filename))
199 result_message_list.append('* %s: [OUTPUT]:' % prefix)
200 for line in output.splitlines():
201 result_message_list.append(' ' + line)
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000202 publish_result(result_message_list, args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000203 return 1
204 return 0
205
206
207def validate_file(args, path):
Milosz Wasilewskia85631a2020-03-02 11:21:29 +0000208 if args.verbose:
209 print("Validating file: %s" % path)
Chase Qi827203c2019-03-07 16:19:15 +0800210 filetype = magic.from_file(path, mime=True)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000211 exitcode = 0
Chase Qi827203c2019-03-07 16:19:15 +0800212 # libmagic takes yaml as 'text/plain', so use file extension here.
Chase Qi9a5f4cf2019-03-25 11:41:55 +0800213 if path.endswith((".yaml", ".yml")):
Milosz Wasilewskid0b78132016-11-22 19:21:14 +0000214 exitcode = validate_yaml(path, args)
215 if exitcode == 0:
216 # if yaml isn't valid there is no point in checking metadata
Dan Rue8ec540c2018-01-25 14:38:12 -0600217 exitcode = validate_yaml_contents(path, args)
Chase Qi827203c2019-03-07 16:19:15 +0800218 elif run_pycodestyle and filetype == "text/x-python":
Chase Qi30290532018-11-20 18:54:43 +0800219 exitcode = pycodestyle_check(path, args)
Chase Qi827203c2019-03-07 16:19:15 +0800220 elif filetype == "text/x-php":
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000221 exitcode = validate_php(path, args)
Chase Qi9a5f4cf2019-03-25 11:41:55 +0800222 elif path.endswith('.sh') or filetype == "text/x-shellscript":
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000223 exitcode = validate_shell(path, args)
Chase Qi827203c2019-03-07 16:19:15 +0800224 else:
225 publish_result(["* UNKNOWN [SKIPPED]: " + path + " - Unknown file type detected: " + filetype], args)
226 return 0
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000227 return exitcode
228
229
230def run_unit_tests(args, filelist=None):
231 exitcode = 0
232 if filelist is not None:
233 for filename in filelist:
Milosz Wasilewskidb499392016-11-07 19:43:51 +0000234 tmp_exitcode = validate_file(args, filename)
235 if tmp_exitcode != 0:
236 exitcode = 1
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000237 else:
238 for root, dirs, files in os.walk('.'):
239 if not root.startswith("./.git"):
240 for name in files:
Milosz Wasilewskidb499392016-11-07 19:43:51 +0000241 tmp_exitcode = validate_file(
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000242 args,
243 root + "/" + name)
Milosz Wasilewskidb499392016-11-07 19:43:51 +0000244 if tmp_exitcode != 0:
245 exitcode = 1
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000246 return exitcode
247
248
249def main(args):
250 exitcode = 0
251 if args.git_latest:
252 # check if git exists
253 git_status, git_result = subprocess.getstatusoutput(
Chase Qi6b5b4652019-01-11 14:18:28 +0800254 "git diff --name-only HEAD~1")
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000255 if git_status == 0:
256 filelist = git_result.split()
257 exitcode = run_unit_tests(args, filelist)
258 elif len(args.file_path) > 0:
259 exitcode = run_unit_tests(args, [args.file_path])
260 else:
261 exitcode = run_unit_tests(args)
262 exit(exitcode)
263
264
265if __name__ == '__main__':
266 parser = argparse.ArgumentParser()
267 parser.add_argument("-p",
Chase Qi30290532018-11-20 18:54:43 +0800268 "--pycodestyle-ignore",
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +0000269 nargs="*",
Milosz Wasilewskiab7645a2016-11-07 10:45:34 +0000270 default=["E501"],
Chase Qi30290532018-11-20 18:54:43 +0800271 help="Space separated list of pycodestyle exclusions",
272 dest="pycodestyle_ignore")
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000273 parser.add_argument("-s",
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +0000274 "--shellcheck-ignore",
275 nargs="*",
276 help="Space separated list of shellcheck exclusions",
277 dest="shellcheck_ignore")
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000278 parser.add_argument("-g",
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +0000279 "--git-latest",
280 action="store_true",
281 default=False,
282 help="If set, the script will try to evaluate files in last git \
283 commit instead of the whole repository",
284 dest="git_latest")
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000285 parser.add_argument("-f",
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +0000286 "--file-path",
287 default="",
288 help="Path to the file that should be checked",
289 dest="file_path")
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000290 parser.add_argument("-r",
291 "--result-file",
292 default="build-error.txt",
293 help="Path to the file that contains results in case of failure",
294 dest="result_file")
Milosz Wasilewskia85631a2020-03-02 11:21:29 +0000295 parser.add_argument("-v",
296 "--verbose",
297 action="store_true",
298 default=False,
299 help="Make output more verbose",
300 dest="verbose")
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000301
302 args = parser.parse_args()
303 main(args)