blob: 98c5578c77780b692963fe5f0b89914131c3cd72 [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
Chase Qi827203c2019-03-07 16:19:15 +08008import magic
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +00009
Chase Qi30290532018-11-20 18:54:43 +080010run_pycodestyle = False
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000011try:
Chase Qi30290532018-11-20 18:54:43 +080012 import pycodestyle
13 run_pycodestyle = True
14except ImportError:
15 print("pycodestyle is not available!")
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000016
17
18def print_stderr(message):
19 sys.stderr.write(message)
20 sys.stderr.write("\n")
21
22
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +000023def publish_result(result_message_list, args):
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000024 result_message = '\n'.join(result_message_list)
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +000025 try:
26 f = open(args.result_file, 'a')
27 f.write("\n\n")
28 f.write(result_message)
29 f.write("\n\n")
30 f.close()
31 except IOError as e:
32 print_stderr("Cannot write to result file: %s" % args.result_file)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000033 print_stderr(result_message)
34
35
Dan Rue8ec540c2018-01-25 14:38:12 -060036def detect_abi():
37 # Retrieve the current canonical abi from
38 # automated/lib/sh-test-lib:detect_abi
39 return subprocess.check_output(
40 ". automated/lib/sh-test-lib && detect_abi && echo $abi",
41 shell=True).decode('utf-8').strip()
42
43
Chase Qi30290532018-11-20 18:54:43 +080044def pycodestyle_check(filepath, args):
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000045 _fmt = "%(row)d:%(col)d: %(code)s %(text)s"
46 options = {
Chase Qi30290532018-11-20 18:54:43 +080047 'ignore': args.pycodestyle_ignore,
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000048 "show_source": True}
Chase Qi30290532018-11-20 18:54:43 +080049 pycodestyle_checker = pycodestyle.StyleGuide(options)
50 fchecker = pycodestyle_checker.checker_class(
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000051 filepath,
Chase Qi30290532018-11-20 18:54:43 +080052 options=pycodestyle_checker.options)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000053 fchecker.check_all()
54 if fchecker.report.file_errors > 0:
55 result_message_list = []
Chase Qi30290532018-11-20 18:54:43 +080056 result_message_list.append("* PYCODESTYLE: [FAILED]: " + filepath)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000057 fchecker.report.print_statistics()
58 for line_number, offset, code, text, doc in fchecker.report._deferred_print:
59 result_message_list.append(
60 _fmt % {
61 'path': filepath,
62 'row': fchecker.report.line_offset + line_number,
63 'col': offset + 1,
64 'code': code, 'text': text,
65 })
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +000066 publish_result(result_message_list, args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000067 return 1
68 else:
Chase Qi30290532018-11-20 18:54:43 +080069 message = "* PYCODESTYLE: [PASSED]: " + filepath
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000070 print_stderr(message)
71 return 0
72
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +000073
Dan Rue8ec540c2018-01-25 14:38:12 -060074def validate_yaml_contents(filepath, args):
Chase Qi827203c2019-03-07 16:19:15 +080075 def validate_testdef_yaml(y, args):
Milosz Wasilewskic69b4092017-10-20 14:04:52 +010076 result_message_list = []
Milosz Wasilewskic69b4092017-10-20 14:04:52 +010077 if 'metadata' not in y.keys():
78 result_message_list.append("* METADATA [FAILED]: " + filepath)
79 result_message_list.append("\tmetadata section missing")
80 publish_result(result_message_list, args)
81 exit(1)
82 metadata_dict = y['metadata']
83 mandatory_keys = set([
84 'name',
85 'format',
86 'description',
87 'maintainer',
88 'os',
89 'devices'])
90 if not mandatory_keys.issubset(set(metadata_dict.keys())):
91 result_message_list.append("* METADATA [FAILED]: " + filepath)
92 result_message_list.append("\tmandatory keys missing: %s" %
93 mandatory_keys.difference(set(metadata_dict.keys())))
94 result_message_list.append("\tactual keys present: %s" %
95 metadata_dict.keys())
96 publish_result(result_message_list, args)
97 return 1
98 for key in mandatory_keys:
99 if len(metadata_dict[key]) == 0:
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000100 result_message_list.append("* METADATA [FAILED]: " + filepath)
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100101 result_message_list.append("\t%s has no content" % key)
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000102 publish_result(result_message_list, args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000103 return 1
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100104 result_message_list.append("* METADATA [PASSED]: " + filepath)
105 publish_result(result_message_list, args)
Dan Rue8ec540c2018-01-25 14:38:12 -0600106 return 0
107
108 def validate_skipgen_yaml(filepath, args):
109 abi = detect_abi()
110 # Run skipgen on skipgen yaml file to check for output and errors
111 skips = subprocess.check_output(
112 "automated/bin/{}/skipgen {}".format(abi, filepath),
113 shell=True).decode('utf-8').strip()
114 if len(skips.split('\n')) < 1:
115 publish_result(["* SKIPGEN [FAILED]: " + filepath + " - No skips found"], args)
116 return 1
117 publish_result(["* SKIPGEN [PASSED]: " + filepath], args)
118 return 0
119
120 filecontent = None
121 try:
122 with open(filepath, "r") as f:
123 filecontent = f.read()
124 except FileNotFoundError:
125 publish_result(["* YAMLVALIDCONTENTS [PASSED]: " + filepath + " - deleted"], args)
126 return 0
127 y = yaml.load(filecontent)
Chase Qi827203c2019-03-07 16:19:15 +0800128 if 'run' in y.keys():
129 # test definition yaml file
130 return validate_testdef_yaml(y, args)
Dan Rue8ec540c2018-01-25 14:38:12 -0600131 elif 'skiplist' in y.keys():
132 # skipgen yaml file
133 return validate_skipgen_yaml(filepath, args)
134 else:
Chase Qi827203c2019-03-07 16:19:15 +0800135 publish_result(["* YAMLVALIDCONTENTS [SKIPPED]: " + filepath + " - Unknown yaml type detected"], args)
136 return 0
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000137
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +0000138
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000139def validate_yaml(filename, args):
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100140 filecontent = None
141 try:
142 with open(filename, "r") as f:
143 filecontent = f.read()
144 except FileNotFoundError:
145 publish_result(["* YAMLVALID [PASSED]: " + filename + " - deleted"], args)
146 return 0
147 try:
Chase Qied017432018-12-14 16:56:20 +0800148 yaml.load(filecontent)
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100149 message = "* YAMLVALID: [PASSED]: " + filename
150 print_stderr(message)
Chase Qi30290532018-11-20 18:54:43 +0800151 except yaml.YAMLError:
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100152 message = "* YAMLVALID: [FAILED]: " + filename
153 result_message_list = []
154 result_message_list.append(message)
155 result_message_list.append("\n\n")
156 exc_type, exc_value, exc_traceback = sys.exc_info()
157 for line in traceback.format_exception_only(exc_type, exc_value):
158 result_message_list.append(' ' + line)
159 publish_result(result_message_list, args)
160 return 1
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000161 return 0
162
163
164def validate_shell(filename, ignore_options):
165 ignore_string = ""
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000166 if args.shellcheck_ignore is not None:
Chase Qied017432018-12-14 16:56:20 +0800167 # Exclude types of warnings in the following format:
168 # -e CODE1,CODE2..
169 ignore_string = "-e %s" % ",".join(args.shellcheck_ignore)
Milosz Wasilewskiab7645a2016-11-07 10:45:34 +0000170 if len(ignore_string) < 4: # contains only "-e "
171 ignore_string = ""
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000172 cmd = 'shellcheck %s' % ignore_string
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000173 return validate_external(cmd, filename, "SHELLCHECK", args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000174
175
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000176def validate_php(filename, args):
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000177 cmd = 'php -l'
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000178 return validate_external(cmd, filename, "PHPLINT", args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000179
180
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000181def validate_external(cmd, filename, prefix, args):
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000182 final_cmd = "%s %s 2>&1" % (cmd, filename)
183 status, output = subprocess.getstatusoutput(final_cmd)
184 if status == 0:
185 message = '* %s: [PASSED]: %s' % (prefix, filename)
186 print_stderr(message)
187 else:
188 result_message_list = []
189 result_message_list.append('* %s: [FAILED]: %s' % (prefix, filename))
190 result_message_list.append('* %s: [OUTPUT]:' % prefix)
191 for line in output.splitlines():
192 result_message_list.append(' ' + line)
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000193 publish_result(result_message_list, args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000194 return 1
195 return 0
196
197
198def validate_file(args, path):
Chase Qi827203c2019-03-07 16:19:15 +0800199 filetype = magic.from_file(path, mime=True)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000200 exitcode = 0
Chase Qi827203c2019-03-07 16:19:15 +0800201 # libmagic takes yaml as 'text/plain', so use file extension here.
202 if path.endswith((".yaml", "yml")):
Milosz Wasilewskid0b78132016-11-22 19:21:14 +0000203 exitcode = validate_yaml(path, args)
204 if exitcode == 0:
205 # if yaml isn't valid there is no point in checking metadata
Dan Rue8ec540c2018-01-25 14:38:12 -0600206 exitcode = validate_yaml_contents(path, args)
Chase Qi827203c2019-03-07 16:19:15 +0800207 elif run_pycodestyle and filetype == "text/x-python":
Chase Qi30290532018-11-20 18:54:43 +0800208 exitcode = pycodestyle_check(path, args)
Chase Qi827203c2019-03-07 16:19:15 +0800209 elif filetype == "text/x-php":
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000210 exitcode = validate_php(path, args)
Chase Qi827203c2019-03-07 16:19:15 +0800211 elif filetype == "text/x-shellscript":
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000212 exitcode = validate_shell(path, args)
Chase Qi827203c2019-03-07 16:19:15 +0800213 else:
214 publish_result(["* UNKNOWN [SKIPPED]: " + path + " - Unknown file type detected: " + filetype], args)
215 return 0
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000216 return exitcode
217
218
219def run_unit_tests(args, filelist=None):
220 exitcode = 0
221 if filelist is not None:
222 for filename in filelist:
Milosz Wasilewskidb499392016-11-07 19:43:51 +0000223 tmp_exitcode = validate_file(args, filename)
224 if tmp_exitcode != 0:
225 exitcode = 1
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000226 else:
227 for root, dirs, files in os.walk('.'):
228 if not root.startswith("./.git"):
229 for name in files:
Milosz Wasilewskidb499392016-11-07 19:43:51 +0000230 tmp_exitcode = validate_file(
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000231 args,
232 root + "/" + name)
Milosz Wasilewskidb499392016-11-07 19:43:51 +0000233 if tmp_exitcode != 0:
234 exitcode = 1
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000235 return exitcode
236
237
238def main(args):
239 exitcode = 0
240 if args.git_latest:
241 # check if git exists
242 git_status, git_result = subprocess.getstatusoutput(
Chase Qi6b5b4652019-01-11 14:18:28 +0800243 "git diff --name-only HEAD~1")
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000244 if git_status == 0:
245 filelist = git_result.split()
246 exitcode = run_unit_tests(args, filelist)
247 elif len(args.file_path) > 0:
248 exitcode = run_unit_tests(args, [args.file_path])
249 else:
250 exitcode = run_unit_tests(args)
251 exit(exitcode)
252
253
254if __name__ == '__main__':
255 parser = argparse.ArgumentParser()
256 parser.add_argument("-p",
Chase Qi30290532018-11-20 18:54:43 +0800257 "--pycodestyle-ignore",
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +0000258 nargs="*",
Milosz Wasilewskiab7645a2016-11-07 10:45:34 +0000259 default=["E501"],
Chase Qi30290532018-11-20 18:54:43 +0800260 help="Space separated list of pycodestyle exclusions",
261 dest="pycodestyle_ignore")
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000262 parser.add_argument("-s",
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +0000263 "--shellcheck-ignore",
264 nargs="*",
265 help="Space separated list of shellcheck exclusions",
266 dest="shellcheck_ignore")
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000267 parser.add_argument("-g",
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +0000268 "--git-latest",
269 action="store_true",
270 default=False,
271 help="If set, the script will try to evaluate files in last git \
272 commit instead of the whole repository",
273 dest="git_latest")
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000274 parser.add_argument("-f",
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +0000275 "--file-path",
276 default="",
277 help="Path to the file that should be checked",
278 dest="file_path")
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000279 parser.add_argument("-r",
280 "--result-file",
281 default="build-error.txt",
282 help="Path to the file that contains results in case of failure",
283 dest="result_file")
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000284
285 args = parser.parse_args()
286 main(args)