blob: 6bf199fffe17f7d14a1636e11e7875d187ee1be0 [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')
35 f.write("\n\n")
36 f.write(result_message)
37 f.write("\n\n")
38 f.close()
39 except IOError as e:
Chase Qi9a5f4cf2019-03-25 11:41:55 +080040 print(e)
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +000041 print_stderr("Cannot write to result file: %s" % args.result_file)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000042 print_stderr(result_message)
43
44
Dan Rue8ec540c2018-01-25 14:38:12 -060045def detect_abi():
46 # Retrieve the current canonical abi from
47 # automated/lib/sh-test-lib:detect_abi
48 return subprocess.check_output(
49 ". automated/lib/sh-test-lib && detect_abi && echo $abi",
50 shell=True).decode('utf-8').strip()
51
52
Chase Qi30290532018-11-20 18:54:43 +080053def pycodestyle_check(filepath, args):
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000054 _fmt = "%(row)d:%(col)d: %(code)s %(text)s"
55 options = {
Chase Qi30290532018-11-20 18:54:43 +080056 'ignore': args.pycodestyle_ignore,
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000057 "show_source": True}
Chase Qi30290532018-11-20 18:54:43 +080058 pycodestyle_checker = pycodestyle.StyleGuide(options)
59 fchecker = pycodestyle_checker.checker_class(
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000060 filepath,
Chase Qi30290532018-11-20 18:54:43 +080061 options=pycodestyle_checker.options)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000062 fchecker.check_all()
63 if fchecker.report.file_errors > 0:
64 result_message_list = []
Chase Qi30290532018-11-20 18:54:43 +080065 result_message_list.append("* PYCODESTYLE: [FAILED]: " + filepath)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000066 fchecker.report.print_statistics()
67 for line_number, offset, code, text, doc in fchecker.report._deferred_print:
68 result_message_list.append(
69 _fmt % {
70 'path': filepath,
71 'row': fchecker.report.line_offset + line_number,
72 'col': offset + 1,
73 'code': code, 'text': text,
74 })
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +000075 publish_result(result_message_list, args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000076 return 1
77 else:
Chase Qi30290532018-11-20 18:54:43 +080078 message = "* PYCODESTYLE: [PASSED]: " + filepath
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000079 print_stderr(message)
80 return 0
81
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +000082
Dan Rue8ec540c2018-01-25 14:38:12 -060083def validate_yaml_contents(filepath, args):
Chase Qi827203c2019-03-07 16:19:15 +080084 def validate_testdef_yaml(y, args):
Milosz Wasilewskic69b4092017-10-20 14:04:52 +010085 result_message_list = []
Milosz Wasilewskic69b4092017-10-20 14:04:52 +010086 if 'metadata' not in y.keys():
87 result_message_list.append("* METADATA [FAILED]: " + filepath)
88 result_message_list.append("\tmetadata section missing")
89 publish_result(result_message_list, args)
90 exit(1)
91 metadata_dict = y['metadata']
92 mandatory_keys = set([
93 'name',
94 'format',
95 'description',
96 'maintainer',
97 'os',
98 'devices'])
99 if not mandatory_keys.issubset(set(metadata_dict.keys())):
100 result_message_list.append("* METADATA [FAILED]: " + filepath)
101 result_message_list.append("\tmandatory keys missing: %s" %
102 mandatory_keys.difference(set(metadata_dict.keys())))
103 result_message_list.append("\tactual keys present: %s" %
104 metadata_dict.keys())
105 publish_result(result_message_list, args)
106 return 1
107 for key in mandatory_keys:
108 if len(metadata_dict[key]) == 0:
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000109 result_message_list.append("* METADATA [FAILED]: " + filepath)
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100110 result_message_list.append("\t%s has no content" % key)
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000111 publish_result(result_message_list, args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000112 return 1
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100113 result_message_list.append("* METADATA [PASSED]: " + filepath)
114 publish_result(result_message_list, args)
Dan Rue8ec540c2018-01-25 14:38:12 -0600115 return 0
116
117 def validate_skipgen_yaml(filepath, args):
118 abi = detect_abi()
119 # Run skipgen on skipgen yaml file to check for output and errors
120 skips = subprocess.check_output(
121 "automated/bin/{}/skipgen {}".format(abi, filepath),
122 shell=True).decode('utf-8').strip()
123 if len(skips.split('\n')) < 1:
124 publish_result(["* SKIPGEN [FAILED]: " + filepath + " - No skips found"], args)
125 return 1
126 publish_result(["* SKIPGEN [PASSED]: " + filepath], args)
127 return 0
128
129 filecontent = None
130 try:
131 with open(filepath, "r") as f:
132 filecontent = f.read()
133 except FileNotFoundError:
134 publish_result(["* YAMLVALIDCONTENTS [PASSED]: " + filepath + " - deleted"], args)
135 return 0
136 y = yaml.load(filecontent)
Chase Qi827203c2019-03-07 16:19:15 +0800137 if 'run' in y.keys():
138 # test definition yaml file
139 return validate_testdef_yaml(y, args)
Dan Rue8ec540c2018-01-25 14:38:12 -0600140 elif 'skiplist' in y.keys():
141 # skipgen yaml file
142 return validate_skipgen_yaml(filepath, args)
143 else:
Chase Qi827203c2019-03-07 16:19:15 +0800144 publish_result(["* YAMLVALIDCONTENTS [SKIPPED]: " + filepath + " - Unknown yaml type detected"], args)
145 return 0
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000146
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +0000147
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000148def validate_yaml(filename, args):
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100149 filecontent = None
150 try:
151 with open(filename, "r") as f:
152 filecontent = f.read()
153 except FileNotFoundError:
154 publish_result(["* YAMLVALID [PASSED]: " + filename + " - deleted"], args)
155 return 0
156 try:
Chase Qied017432018-12-14 16:56:20 +0800157 yaml.load(filecontent)
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100158 message = "* YAMLVALID: [PASSED]: " + filename
159 print_stderr(message)
Chase Qi30290532018-11-20 18:54:43 +0800160 except yaml.YAMLError:
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100161 message = "* YAMLVALID: [FAILED]: " + filename
162 result_message_list = []
163 result_message_list.append(message)
164 result_message_list.append("\n\n")
165 exc_type, exc_value, exc_traceback = sys.exc_info()
166 for line in traceback.format_exception_only(exc_type, exc_value):
167 result_message_list.append(' ' + line)
168 publish_result(result_message_list, args)
169 return 1
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000170 return 0
171
172
173def validate_shell(filename, ignore_options):
174 ignore_string = ""
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000175 if args.shellcheck_ignore is not None:
Chase Qied017432018-12-14 16:56:20 +0800176 # Exclude types of warnings in the following format:
177 # -e CODE1,CODE2..
178 ignore_string = "-e %s" % ",".join(args.shellcheck_ignore)
Milosz Wasilewskiab7645a2016-11-07 10:45:34 +0000179 if len(ignore_string) < 4: # contains only "-e "
180 ignore_string = ""
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000181 cmd = 'shellcheck %s' % ignore_string
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000182 return validate_external(cmd, filename, "SHELLCHECK", args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000183
184
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000185def validate_php(filename, args):
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000186 cmd = 'php -l'
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000187 return validate_external(cmd, filename, "PHPLINT", args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000188
189
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000190def validate_external(cmd, filename, prefix, args):
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000191 final_cmd = "%s %s 2>&1" % (cmd, filename)
192 status, output = subprocess.getstatusoutput(final_cmd)
193 if status == 0:
194 message = '* %s: [PASSED]: %s' % (prefix, filename)
195 print_stderr(message)
196 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):
Chase Qi827203c2019-03-07 16:19:15 +0800208 filetype = magic.from_file(path, mime=True)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000209 exitcode = 0
Chase Qi827203c2019-03-07 16:19:15 +0800210 # libmagic takes yaml as 'text/plain', so use file extension here.
Chase Qi9a5f4cf2019-03-25 11:41:55 +0800211 if path.endswith((".yaml", ".yml")):
Milosz Wasilewskid0b78132016-11-22 19:21:14 +0000212 exitcode = validate_yaml(path, args)
213 if exitcode == 0:
214 # if yaml isn't valid there is no point in checking metadata
Dan Rue8ec540c2018-01-25 14:38:12 -0600215 exitcode = validate_yaml_contents(path, args)
Chase Qi827203c2019-03-07 16:19:15 +0800216 elif run_pycodestyle and filetype == "text/x-python":
Chase Qi30290532018-11-20 18:54:43 +0800217 exitcode = pycodestyle_check(path, args)
Chase Qi827203c2019-03-07 16:19:15 +0800218 elif filetype == "text/x-php":
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000219 exitcode = validate_php(path, args)
Chase Qi9a5f4cf2019-03-25 11:41:55 +0800220 elif path.endswith('.sh') or filetype == "text/x-shellscript":
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000221 exitcode = validate_shell(path, args)
Chase Qi827203c2019-03-07 16:19:15 +0800222 else:
223 publish_result(["* UNKNOWN [SKIPPED]: " + path + " - Unknown file type detected: " + filetype], args)
224 return 0
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000225 return exitcode
226
227
228def run_unit_tests(args, filelist=None):
229 exitcode = 0
230 if filelist is not None:
231 for filename in filelist:
Milosz Wasilewskidb499392016-11-07 19:43:51 +0000232 tmp_exitcode = validate_file(args, filename)
233 if tmp_exitcode != 0:
234 exitcode = 1
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000235 else:
236 for root, dirs, files in os.walk('.'):
237 if not root.startswith("./.git"):
238 for name in files:
Milosz Wasilewskidb499392016-11-07 19:43:51 +0000239 tmp_exitcode = validate_file(
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000240 args,
241 root + "/" + name)
Milosz Wasilewskidb499392016-11-07 19:43:51 +0000242 if tmp_exitcode != 0:
243 exitcode = 1
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000244 return exitcode
245
246
247def main(args):
248 exitcode = 0
249 if args.git_latest:
250 # check if git exists
251 git_status, git_result = subprocess.getstatusoutput(
Chase Qi6b5b4652019-01-11 14:18:28 +0800252 "git diff --name-only HEAD~1")
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000253 if git_status == 0:
254 filelist = git_result.split()
255 exitcode = run_unit_tests(args, filelist)
256 elif len(args.file_path) > 0:
257 exitcode = run_unit_tests(args, [args.file_path])
258 else:
259 exitcode = run_unit_tests(args)
260 exit(exitcode)
261
262
263if __name__ == '__main__':
264 parser = argparse.ArgumentParser()
265 parser.add_argument("-p",
Chase Qi30290532018-11-20 18:54:43 +0800266 "--pycodestyle-ignore",
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +0000267 nargs="*",
Milosz Wasilewskiab7645a2016-11-07 10:45:34 +0000268 default=["E501"],
Chase Qi30290532018-11-20 18:54:43 +0800269 help="Space separated list of pycodestyle exclusions",
270 dest="pycodestyle_ignore")
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000271 parser.add_argument("-s",
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +0000272 "--shellcheck-ignore",
273 nargs="*",
274 help="Space separated list of shellcheck exclusions",
275 dest="shellcheck_ignore")
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000276 parser.add_argument("-g",
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +0000277 "--git-latest",
278 action="store_true",
279 default=False,
280 help="If set, the script will try to evaluate files in last git \
281 commit instead of the whole repository",
282 dest="git_latest")
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000283 parser.add_argument("-f",
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +0000284 "--file-path",
285 default="",
286 help="Path to the file that should be checked",
287 dest="file_path")
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000288 parser.add_argument("-r",
289 "--result-file",
290 default="build-error.txt",
291 help="Path to the file that contains results in case of failure",
292 dest="result_file")
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000293
294 args = parser.parse_args()
295 main(args)