blob: 7406fdf7ee4461b412fd4bd3a9fe94bea55d2b7f [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
Benjamin Copeland15d743e2021-02-22 08:35:10 +000012
Chase Qi30290532018-11-20 18:54:43 +080013 run_pycodestyle = True
Chase Qif70267b2019-03-13 12:23:37 +080014except ImportError as e:
15 print(e)
16 print("Install pycodestyle: pip3 install pycodestyle")
17 sys.exit(1)
18
19try:
20 import magic
21except ImportError as e:
22 print(e)
23 print("Install python-magic: pip3 install python-magic")
24 sys.exit(1)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000025
26
27def print_stderr(message):
28 sys.stderr.write(message)
29 sys.stderr.write("\n")
30
31
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +000032def publish_result(result_message_list, args):
Milosz Wasilewski191b4772020-06-30 13:31:17 +010033 if result_message_list:
Benjamin Copeland15d743e2021-02-22 08:35:10 +000034 result_message = "\n".join(result_message_list)
Milosz Wasilewski191b4772020-06-30 13:31:17 +010035 try:
Benjamin Copeland15d743e2021-02-22 08:35:10 +000036 f = open(args.result_file, "a")
Milosz Wasilewski191b4772020-06-30 13:31:17 +010037 f.write(result_message)
38 f.write("\n")
39 f.close()
40 except IOError as e:
41 print(e)
42 print_stderr("Cannot write to result file: %s" % args.result_file)
43 if args.verbose:
44 print_stderr(result_message)
45 else:
Benjamin Copeland15d743e2021-02-22 08:35:10 +000046 result_message = "\n".join(args.failed_message_list)
Milosz Wasilewski191b4772020-06-30 13:31:17 +010047 print_stderr(result_message)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000048
49
Dan Rue8ec540c2018-01-25 14:38:12 -060050def detect_abi():
51 # Retrieve the current canonical abi from
52 # automated/lib/sh-test-lib:detect_abi
Benjamin Copeland15d743e2021-02-22 08:35:10 +000053 return (
54 subprocess.check_output(
55 ". automated/lib/sh-test-lib && detect_abi && echo $abi", shell=True
56 )
57 .decode("utf-8")
58 .strip()
59 )
Dan Rue8ec540c2018-01-25 14:38:12 -060060
61
Chase Qi30290532018-11-20 18:54:43 +080062def pycodestyle_check(filepath, args):
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000063 _fmt = "%(row)d:%(col)d: %(code)s %(text)s"
Benjamin Copeland15d743e2021-02-22 08:35:10 +000064 options = {"ignore": args.pycodestyle_ignore, "show_source": True}
Chase Qi30290532018-11-20 18:54:43 +080065 pycodestyle_checker = pycodestyle.StyleGuide(options)
66 fchecker = pycodestyle_checker.checker_class(
Benjamin Copeland15d743e2021-02-22 08:35:10 +000067 filepath, options=pycodestyle_checker.options
68 )
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000069 fchecker.check_all()
70 if fchecker.report.file_errors > 0:
71 result_message_list = []
Chase Qi30290532018-11-20 18:54:43 +080072 result_message_list.append("* PYCODESTYLE: [FAILED]: " + filepath)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000073 fchecker.report.print_statistics()
74 for line_number, offset, code, text, doc in fchecker.report._deferred_print:
75 result_message_list.append(
Benjamin Copeland15d743e2021-02-22 08:35:10 +000076 _fmt
77 % {
78 "path": filepath,
79 "row": fchecker.report.line_offset + line_number,
80 "col": offset + 1,
81 "code": code,
82 "text": text,
83 }
84 )
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +000085 publish_result(result_message_list, args)
Milosz Wasilewski191b4772020-06-30 13:31:17 +010086 args.failed_message_list = args.failed_message_list + result_message_list
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000087 return 1
88 else:
Milosz Wasilewski191b4772020-06-30 13:31:17 +010089 if args.verbose:
90 message = "* PYCODESTYLE: [PASSED]: " + filepath
91 print_stderr(message)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000092 return 0
93
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +000094
Dan Rue8ec540c2018-01-25 14:38:12 -060095def validate_yaml_contents(filepath, args):
Chase Qi827203c2019-03-07 16:19:15 +080096 def validate_testdef_yaml(y, args):
Milosz Wasilewskic69b4092017-10-20 14:04:52 +010097 result_message_list = []
Benjamin Copeland15d743e2021-02-22 08:35:10 +000098 if "metadata" not in y.keys():
Milosz Wasilewskic69b4092017-10-20 14:04:52 +010099 result_message_list.append("* METADATA [FAILED]: " + filepath)
100 result_message_list.append("\tmetadata section missing")
101 publish_result(result_message_list, args)
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100102 args.failed_message_list = args.failed_message_list + result_message_list
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100103 exit(1)
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000104 metadata_dict = y["metadata"]
105 mandatory_keys = set(
106 ["name", "format", "description", "maintainer", "os", "devices"]
107 )
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100108 if not mandatory_keys.issubset(set(metadata_dict.keys())):
109 result_message_list.append("* METADATA [FAILED]: " + filepath)
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000110 result_message_list.append(
111 "\tmandatory keys missing: %s"
112 % mandatory_keys.difference(set(metadata_dict.keys()))
113 )
114 result_message_list.append(
115 "\tactual keys present: %s" % metadata_dict.keys()
116 )
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100117 publish_result(result_message_list, args)
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100118 args.failed_message_list = args.failed_message_list + result_message_list
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100119 return 1
120 for key in mandatory_keys:
121 if len(metadata_dict[key]) == 0:
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000122 result_message_list.append("* METADATA [FAILED]: " + filepath)
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100123 result_message_list.append("\t%s has no content" % key)
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000124 publish_result(result_message_list, args)
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000125 args.failed_message_list = (
126 args.failed_message_list + result_message_list
127 )
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000128 return 1
Milosz Wasilewski43c84d12020-03-20 16:41:02 +0000129 # check if name has white spaces
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000130 if metadata_dict["name"].find(" ") > -1:
Milosz Wasilewski43c84d12020-03-20 16:41:02 +0000131 result_message_list.append("* METADATA [FAILED]: " + filepath)
132 result_message_list.append("\t'name' contains whitespace")
133 publish_result(result_message_list, args)
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100134 args.failed_message_list = args.ailed_message_list + result_message_list
Milosz Wasilewski43c84d12020-03-20 16:41:02 +0000135 return 1
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100136 # check 'format' value
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000137 if metadata_dict["format"] not in [
138 "Lava-Test Test Definition 1.0",
139 "Manual Test Definition 1.0",
140 ]:
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100141 result_message_list.append("* METADATA [FAILED]: " + filepath)
142 result_message_list.append("\t'format' has incorrect value")
143 publish_result(result_message_list, args)
144 args.failed_message_list = args.failed_message_list + result_message_list
145 return 1
146
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100147 result_message_list.append("* METADATA [PASSED]: " + filepath)
148 publish_result(result_message_list, args)
Dan Rue8ec540c2018-01-25 14:38:12 -0600149 return 0
150
151 def validate_skipgen_yaml(filepath, args):
152 abi = detect_abi()
153 # Run skipgen on skipgen yaml file to check for output and errors
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000154 skips = (
155 subprocess.check_output(
156 "automated/bin/{}/skipgen {}".format(abi, filepath), shell=True
157 )
158 .decode("utf-8")
159 .strip()
160 )
161 if len(skips.split("\n")) < 1:
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100162 message = "* SKIPGEN [FAILED]: " + filepath + " - No skips found"
163 publish_result([message], args)
164 args.failed_message_list.append(message)
Dan Rue8ec540c2018-01-25 14:38:12 -0600165 return 1
166 publish_result(["* SKIPGEN [PASSED]: " + filepath], args)
167 return 0
168
169 filecontent = None
170 try:
171 with open(filepath, "r") as f:
172 filecontent = f.read()
173 except FileNotFoundError:
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000174 publish_result(
175 ["* YAMLVALIDCONTENTS [PASSED]: " + filepath + " - deleted"], args
176 )
Dan Rue8ec540c2018-01-25 14:38:12 -0600177 return 0
Milosz Wasilewski1097c992020-03-02 11:38:02 +0000178 y = yaml.load(filecontent, Loader=yaml.FullLoader)
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000179 if "run" in y.keys():
Chase Qi827203c2019-03-07 16:19:15 +0800180 # test definition yaml file
181 return validate_testdef_yaml(y, args)
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000182 elif "skiplist" in y.keys():
Dan Rue8ec540c2018-01-25 14:38:12 -0600183 # skipgen yaml file
184 return validate_skipgen_yaml(filepath, args)
185 else:
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000186 publish_result(
187 [
188 "* YAMLVALIDCONTENTS [SKIPPED]: "
189 + filepath
190 + " - Unknown yaml type detected"
191 ],
192 args,
193 )
Chase Qi827203c2019-03-07 16:19:15 +0800194 return 0
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000195
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +0000196
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000197def validate_yaml(filename, args):
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100198 filecontent = None
199 try:
200 with open(filename, "r") as f:
201 filecontent = f.read()
202 except FileNotFoundError:
203 publish_result(["* YAMLVALID [PASSED]: " + filename + " - deleted"], args)
204 return 0
205 try:
Milosz Wasilewski1097c992020-03-02 11:38:02 +0000206 yaml.load(filecontent, Loader=yaml.FullLoader)
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100207 if args.verbose:
208 message = "* YAMLVALID: [PASSED]: " + filename
209 print_stderr(message)
Chase Qi30290532018-11-20 18:54:43 +0800210 except yaml.YAMLError:
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100211 message = "* YAMLVALID: [FAILED]: " + filename
212 result_message_list = []
213 result_message_list.append(message)
214 result_message_list.append("\n\n")
215 exc_type, exc_value, exc_traceback = sys.exc_info()
216 for line in traceback.format_exception_only(exc_type, exc_value):
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000217 result_message_list.append(" " + line)
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100218 publish_result(result_message_list, args)
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100219 args.failed_message_list = args.failed_message_list + result_message_list
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100220 return 1
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000221 return 0
222
223
224def validate_shell(filename, ignore_options):
225 ignore_string = ""
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000226 if args.shellcheck_ignore is not None:
Chase Qied017432018-12-14 16:56:20 +0800227 # Exclude types of warnings in the following format:
228 # -e CODE1,CODE2..
229 ignore_string = "-e %s" % ",".join(args.shellcheck_ignore)
Milosz Wasilewskiab7645a2016-11-07 10:45:34 +0000230 if len(ignore_string) < 4: # contains only "-e "
231 ignore_string = ""
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000232 cmd = "shellcheck %s" % ignore_string
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000233 return validate_external(cmd, filename, "SHELLCHECK", args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000234
235
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000236def validate_php(filename, args):
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000237 cmd = "php -l"
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000238 return validate_external(cmd, filename, "PHPLINT", args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000239
240
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000241def validate_external(cmd, filename, prefix, args):
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000242 final_cmd = "%s %s 2>&1" % (cmd, filename)
243 status, output = subprocess.getstatusoutput(final_cmd)
244 if status == 0:
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000245 message = "* %s: [PASSED]: %s" % (prefix, filename)
Milosz Wasilewskia85631a2020-03-02 11:21:29 +0000246 publish_result([message], args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000247 else:
248 result_message_list = []
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000249 result_message_list.append("* %s: [FAILED]: %s" % (prefix, filename))
250 result_message_list.append("* %s: [OUTPUT]:" % prefix)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000251 for line in output.splitlines():
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000252 result_message_list.append(" " + line)
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000253 publish_result(result_message_list, args)
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100254 args.failed_message_list = args.failed_message_list + result_message_list
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000255 return 1
256 return 0
257
258
259def validate_file(args, path):
Milosz Wasilewskia85631a2020-03-02 11:21:29 +0000260 if args.verbose:
261 print("Validating file: %s" % path)
Chase Qi827203c2019-03-07 16:19:15 +0800262 filetype = magic.from_file(path, mime=True)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000263 exitcode = 0
Chase Qi827203c2019-03-07 16:19:15 +0800264 # libmagic takes yaml as 'text/plain', so use file extension here.
Chase Qi9a5f4cf2019-03-25 11:41:55 +0800265 if path.endswith((".yaml", ".yml")):
Milosz Wasilewskid0b78132016-11-22 19:21:14 +0000266 exitcode = validate_yaml(path, args)
267 if exitcode == 0:
268 # if yaml isn't valid there is no point in checking metadata
Dan Rue8ec540c2018-01-25 14:38:12 -0600269 exitcode = validate_yaml_contents(path, args)
Chase Qi827203c2019-03-07 16:19:15 +0800270 elif run_pycodestyle and filetype == "text/x-python":
Chase Qi30290532018-11-20 18:54:43 +0800271 exitcode = pycodestyle_check(path, args)
Chase Qi827203c2019-03-07 16:19:15 +0800272 elif filetype == "text/x-php":
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000273 exitcode = validate_php(path, args)
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000274 elif path.endswith(".sh") or filetype == "text/x-shellscript":
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000275 exitcode = validate_shell(path, args)
Chase Qi827203c2019-03-07 16:19:15 +0800276 else:
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000277 publish_result(
278 [
279 "* UNKNOWN [SKIPPED]: "
280 + path
281 + " - Unknown file type detected: "
282 + filetype
283 ],
284 args,
285 )
Chase Qi827203c2019-03-07 16:19:15 +0800286 return 0
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000287 return exitcode
288
289
290def run_unit_tests(args, filelist=None):
291 exitcode = 0
292 if filelist is not None:
293 for filename in filelist:
Milosz Wasilewskidb499392016-11-07 19:43:51 +0000294 tmp_exitcode = validate_file(args, filename)
295 if tmp_exitcode != 0:
296 exitcode = 1
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000297 else:
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000298 for root, dirs, files in os.walk("."):
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000299 if not root.startswith("./.git"):
300 for name in files:
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000301 tmp_exitcode = validate_file(args, root + "/" + name)
Milosz Wasilewskidb499392016-11-07 19:43:51 +0000302 if tmp_exitcode != 0:
303 exitcode = 1
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000304 return exitcode
305
306
307def main(args):
308 exitcode = 0
309 if args.git_latest:
310 # check if git exists
311 git_status, git_result = subprocess.getstatusoutput(
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000312 "git diff --name-only HEAD~1"
313 )
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000314 if git_status == 0:
315 filelist = git_result.split()
316 exitcode = run_unit_tests(args, filelist)
317 elif len(args.file_path) > 0:
318 exitcode = run_unit_tests(args, [args.file_path])
319 else:
320 exitcode = run_unit_tests(args)
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100321 if not args.verbose:
322 publish_result(None, args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000323 exit(exitcode)
324
325
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000326if __name__ == "__main__":
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100327 failed_message_list = []
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000328 parser = argparse.ArgumentParser()
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000329 parser.add_argument(
330 "-p",
331 "--pycodestyle-ignore",
332 nargs="*",
333 default=["E501"],
334 help="Space separated list of pycodestyle exclusions",
335 dest="pycodestyle_ignore",
336 )
337 parser.add_argument(
338 "-s",
339 "--shellcheck-ignore",
340 nargs="*",
341 help="Space separated list of shellcheck exclusions",
342 dest="shellcheck_ignore",
343 )
344 parser.add_argument(
345 "-g",
346 "--git-latest",
347 action="store_true",
348 default=False,
349 help="If set, the script will try to evaluate files in last git \
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +0000350 commit instead of the whole repository",
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000351 dest="git_latest",
352 )
353 parser.add_argument(
354 "-f",
355 "--file-path",
356 default="",
357 help="Path to the file that should be checked",
358 dest="file_path",
359 )
360 parser.add_argument(
361 "-r",
362 "--result-file",
363 default="build-error.txt",
364 help="Path to the file that contains results in case of failure",
365 dest="result_file",
366 )
367 parser.add_argument(
368 "-v",
369 "--verbose",
370 action="store_true",
371 default=False,
372 help="Make output more verbose",
373 dest="verbose",
374 )
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000375
376 args = parser.parse_args()
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100377 setattr(args, "failed_message_list", failed_message_list)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000378 main(args)