blob: aa44eb1af1a47dc1fb12bb467ffa13e80d58429c [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 Wasilewskic64b4272022-06-30 13:15:07 +0100134 args.failed_message_list = args.failed_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
Milosz Wasilewski3c138192025-01-14 19:13:07 +0000224def validate_shell(filename, args):
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000225 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 = ""
Milosz Wasilewski3c138192025-01-14 19:13:07 +0000232 ignore_string = "-S %s %s" % (args.shellcheck_level, ignore_string)
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000233 cmd = "shellcheck %s" % ignore_string
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000234 return validate_external(cmd, filename, "SHELLCHECK", args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000235
236
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000237def validate_php(filename, args):
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000238 cmd = "php -l"
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000239 return validate_external(cmd, filename, "PHPLINT", args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000240
241
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000242def validate_external(cmd, filename, prefix, args):
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000243 final_cmd = "%s %s 2>&1" % (cmd, filename)
244 status, output = subprocess.getstatusoutput(final_cmd)
245 if status == 0:
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000246 message = "* %s: [PASSED]: %s" % (prefix, filename)
Milosz Wasilewskia85631a2020-03-02 11:21:29 +0000247 publish_result([message], args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000248 else:
249 result_message_list = []
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000250 result_message_list.append("* %s: [FAILED]: %s" % (prefix, filename))
251 result_message_list.append("* %s: [OUTPUT]:" % prefix)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000252 for line in output.splitlines():
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000253 result_message_list.append(" " + line)
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000254 publish_result(result_message_list, args)
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100255 args.failed_message_list = args.failed_message_list + result_message_list
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000256 return 1
257 return 0
258
259
260def validate_file(args, path):
Milosz Wasilewskia85631a2020-03-02 11:21:29 +0000261 if args.verbose:
262 print("Validating file: %s" % path)
Chase Qi827203c2019-03-07 16:19:15 +0800263 filetype = magic.from_file(path, mime=True)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000264 exitcode = 0
Chase Qi827203c2019-03-07 16:19:15 +0800265 # libmagic takes yaml as 'text/plain', so use file extension here.
Chase Qi9a5f4cf2019-03-25 11:41:55 +0800266 if path.endswith((".yaml", ".yml")):
Milosz Wasilewskid0b78132016-11-22 19:21:14 +0000267 exitcode = validate_yaml(path, args)
268 if exitcode == 0:
269 # if yaml isn't valid there is no point in checking metadata
Dan Rue8ec540c2018-01-25 14:38:12 -0600270 exitcode = validate_yaml_contents(path, args)
Chase Qi827203c2019-03-07 16:19:15 +0800271 elif run_pycodestyle and filetype == "text/x-python":
Chase Qi30290532018-11-20 18:54:43 +0800272 exitcode = pycodestyle_check(path, args)
Chase Qi827203c2019-03-07 16:19:15 +0800273 elif filetype == "text/x-php":
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000274 exitcode = validate_php(path, args)
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000275 elif path.endswith(".sh") or filetype == "text/x-shellscript":
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000276 exitcode = validate_shell(path, args)
Chase Qi827203c2019-03-07 16:19:15 +0800277 else:
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000278 publish_result(
279 [
280 "* UNKNOWN [SKIPPED]: "
281 + path
282 + " - Unknown file type detected: "
283 + filetype
284 ],
285 args,
286 )
Chase Qi827203c2019-03-07 16:19:15 +0800287 return 0
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000288 return exitcode
289
290
291def run_unit_tests(args, filelist=None):
292 exitcode = 0
293 if filelist is not None:
294 for filename in filelist:
Milosz Wasilewskidb499392016-11-07 19:43:51 +0000295 tmp_exitcode = validate_file(args, filename)
296 if tmp_exitcode != 0:
297 exitcode = 1
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000298 else:
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000299 for root, dirs, files in os.walk("."):
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000300 if not root.startswith("./.git"):
301 for name in files:
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000302 tmp_exitcode = validate_file(args, root + "/" + name)
Milosz Wasilewskidb499392016-11-07 19:43:51 +0000303 if tmp_exitcode != 0:
304 exitcode = 1
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000305 return exitcode
306
307
308def main(args):
309 exitcode = 0
310 if args.git_latest:
311 # check if git exists
312 git_status, git_result = subprocess.getstatusoutput(
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000313 "git diff --name-only HEAD~1"
314 )
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000315 if git_status == 0:
316 filelist = git_result.split()
317 exitcode = run_unit_tests(args, filelist)
318 elif len(args.file_path) > 0:
319 exitcode = run_unit_tests(args, [args.file_path])
320 else:
321 exitcode = run_unit_tests(args)
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100322 if not args.verbose:
323 publish_result(None, args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000324 exit(exitcode)
325
326
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000327if __name__ == "__main__":
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100328 failed_message_list = []
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000329 parser = argparse.ArgumentParser()
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000330 parser.add_argument(
331 "-p",
332 "--pycodestyle-ignore",
333 nargs="*",
334 default=["E501"],
335 help="Space separated list of pycodestyle exclusions",
336 dest="pycodestyle_ignore",
337 )
338 parser.add_argument(
339 "-s",
340 "--shellcheck-ignore",
341 nargs="*",
342 help="Space separated list of shellcheck exclusions",
343 dest="shellcheck_ignore",
344 )
345 parser.add_argument(
Milosz Wasilewski3c138192025-01-14 19:13:07 +0000346 "-l",
347 "--shellcheck-level",
348 default="warning",
349 help="Shellcheck level set with -S",
350 dest="shellcheck_level",
351 )
352
353 parser.add_argument(
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000354 "-g",
355 "--git-latest",
356 action="store_true",
357 default=False,
358 help="If set, the script will try to evaluate files in last git \
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +0000359 commit instead of the whole repository",
Benjamin Copeland15d743e2021-02-22 08:35:10 +0000360 dest="git_latest",
361 )
362 parser.add_argument(
363 "-f",
364 "--file-path",
365 default="",
366 help="Path to the file that should be checked",
367 dest="file_path",
368 )
369 parser.add_argument(
370 "-r",
371 "--result-file",
372 default="build-error.txt",
373 help="Path to the file that contains results in case of failure",
374 dest="result_file",
375 )
376 parser.add_argument(
377 "-v",
378 "--verbose",
379 action="store_true",
380 default=False,
381 help="Make output more verbose",
382 dest="verbose",
383 )
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000384
385 args = parser.parse_args()
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100386 setattr(args, "failed_message_list", failed_message_list)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000387 main(args)