blob: 84004f17232156ff0808f11200e78c2cedfd5e05 [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 Wasilewski191b4772020-06-30 13:31:17 +010032 if result_message_list:
33 result_message = '\n'.join(result_message_list)
34 try:
35 f = open(args.result_file, 'a')
36 f.write(result_message)
37 f.write("\n")
38 f.close()
39 except IOError as e:
40 print(e)
41 print_stderr("Cannot write to result file: %s" % args.result_file)
42 if args.verbose:
43 print_stderr(result_message)
44 else:
45 result_message = '\n'.join(args.failed_message_list)
46 print_stderr(result_message)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000047
48
Dan Rue8ec540c2018-01-25 14:38:12 -060049def detect_abi():
50 # Retrieve the current canonical abi from
51 # automated/lib/sh-test-lib:detect_abi
52 return subprocess.check_output(
53 ". automated/lib/sh-test-lib && detect_abi && echo $abi",
54 shell=True).decode('utf-8').strip()
55
56
Chase Qi30290532018-11-20 18:54:43 +080057def pycodestyle_check(filepath, args):
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000058 _fmt = "%(row)d:%(col)d: %(code)s %(text)s"
59 options = {
Chase Qi30290532018-11-20 18:54:43 +080060 'ignore': args.pycodestyle_ignore,
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000061 "show_source": True}
Chase Qi30290532018-11-20 18:54:43 +080062 pycodestyle_checker = pycodestyle.StyleGuide(options)
63 fchecker = pycodestyle_checker.checker_class(
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000064 filepath,
Chase Qi30290532018-11-20 18:54:43 +080065 options=pycodestyle_checker.options)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000066 fchecker.check_all()
67 if fchecker.report.file_errors > 0:
68 result_message_list = []
Chase Qi30290532018-11-20 18:54:43 +080069 result_message_list.append("* PYCODESTYLE: [FAILED]: " + filepath)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000070 fchecker.report.print_statistics()
71 for line_number, offset, code, text, doc in fchecker.report._deferred_print:
72 result_message_list.append(
73 _fmt % {
74 'path': filepath,
75 'row': fchecker.report.line_offset + line_number,
76 'col': offset + 1,
77 'code': code, 'text': text,
78 })
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +000079 publish_result(result_message_list, args)
Milosz Wasilewski191b4772020-06-30 13:31:17 +010080 args.failed_message_list = args.failed_message_list + result_message_list
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000081 return 1
82 else:
Milosz Wasilewski191b4772020-06-30 13:31:17 +010083 if args.verbose:
84 message = "* PYCODESTYLE: [PASSED]: " + filepath
85 print_stderr(message)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +000086 return 0
87
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +000088
Dan Rue8ec540c2018-01-25 14:38:12 -060089def validate_yaml_contents(filepath, args):
Chase Qi827203c2019-03-07 16:19:15 +080090 def validate_testdef_yaml(y, args):
Milosz Wasilewskic69b4092017-10-20 14:04:52 +010091 result_message_list = []
Milosz Wasilewskic69b4092017-10-20 14:04:52 +010092 if 'metadata' not in y.keys():
93 result_message_list.append("* METADATA [FAILED]: " + filepath)
94 result_message_list.append("\tmetadata section missing")
95 publish_result(result_message_list, args)
Milosz Wasilewski191b4772020-06-30 13:31:17 +010096 args.failed_message_list = args.failed_message_list + result_message_list
Milosz Wasilewskic69b4092017-10-20 14:04:52 +010097 exit(1)
98 metadata_dict = y['metadata']
99 mandatory_keys = set([
100 'name',
101 'format',
102 'description',
103 'maintainer',
104 'os',
105 'devices'])
106 if not mandatory_keys.issubset(set(metadata_dict.keys())):
107 result_message_list.append("* METADATA [FAILED]: " + filepath)
108 result_message_list.append("\tmandatory keys missing: %s" %
109 mandatory_keys.difference(set(metadata_dict.keys())))
110 result_message_list.append("\tactual keys present: %s" %
111 metadata_dict.keys())
112 publish_result(result_message_list, args)
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100113 args.failed_message_list = args.failed_message_list + result_message_list
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100114 return 1
115 for key in mandatory_keys:
116 if len(metadata_dict[key]) == 0:
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000117 result_message_list.append("* METADATA [FAILED]: " + filepath)
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100118 result_message_list.append("\t%s has no content" % key)
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000119 publish_result(result_message_list, args)
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100120 args.failed_message_list = args.failed_message_list + result_message_list
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000121 return 1
Milosz Wasilewski43c84d12020-03-20 16:41:02 +0000122 # check if name has white spaces
123 if metadata_dict['name'].find(" ") > -1:
124 result_message_list.append("* METADATA [FAILED]: " + filepath)
125 result_message_list.append("\t'name' contains whitespace")
126 publish_result(result_message_list, args)
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100127 args.failed_message_list = args.ailed_message_list + result_message_list
Milosz Wasilewski43c84d12020-03-20 16:41:02 +0000128 return 1
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100129 # check 'format' value
130 if metadata_dict['format'] not in ["Lava-Test Test Definition 1.0", "Manual Test Definition 1.0"]:
131 result_message_list.append("* METADATA [FAILED]: " + filepath)
132 result_message_list.append("\t'format' has incorrect value")
133 publish_result(result_message_list, args)
134 args.failed_message_list = args.failed_message_list + result_message_list
135 return 1
136
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100137 result_message_list.append("* METADATA [PASSED]: " + filepath)
138 publish_result(result_message_list, args)
Dan Rue8ec540c2018-01-25 14:38:12 -0600139 return 0
140
141 def validate_skipgen_yaml(filepath, args):
142 abi = detect_abi()
143 # Run skipgen on skipgen yaml file to check for output and errors
144 skips = subprocess.check_output(
145 "automated/bin/{}/skipgen {}".format(abi, filepath),
146 shell=True).decode('utf-8').strip()
147 if len(skips.split('\n')) < 1:
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100148 message = "* SKIPGEN [FAILED]: " + filepath + " - No skips found"
149 publish_result([message], args)
150 args.failed_message_list.append(message)
Dan Rue8ec540c2018-01-25 14:38:12 -0600151 return 1
152 publish_result(["* SKIPGEN [PASSED]: " + filepath], args)
153 return 0
154
155 filecontent = None
156 try:
157 with open(filepath, "r") as f:
158 filecontent = f.read()
159 except FileNotFoundError:
160 publish_result(["* YAMLVALIDCONTENTS [PASSED]: " + filepath + " - deleted"], args)
161 return 0
Milosz Wasilewski1097c992020-03-02 11:38:02 +0000162 y = yaml.load(filecontent, Loader=yaml.FullLoader)
Chase Qi827203c2019-03-07 16:19:15 +0800163 if 'run' in y.keys():
164 # test definition yaml file
165 return validate_testdef_yaml(y, args)
Dan Rue8ec540c2018-01-25 14:38:12 -0600166 elif 'skiplist' in y.keys():
167 # skipgen yaml file
168 return validate_skipgen_yaml(filepath, args)
169 else:
Chase Qi827203c2019-03-07 16:19:15 +0800170 publish_result(["* YAMLVALIDCONTENTS [SKIPPED]: " + filepath + " - Unknown yaml type detected"], args)
171 return 0
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000172
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +0000173
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000174def validate_yaml(filename, args):
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100175 filecontent = None
176 try:
177 with open(filename, "r") as f:
178 filecontent = f.read()
179 except FileNotFoundError:
180 publish_result(["* YAMLVALID [PASSED]: " + filename + " - deleted"], args)
181 return 0
182 try:
Milosz Wasilewski1097c992020-03-02 11:38:02 +0000183 yaml.load(filecontent, Loader=yaml.FullLoader)
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100184 if args.verbose:
185 message = "* YAMLVALID: [PASSED]: " + filename
186 print_stderr(message)
Chase Qi30290532018-11-20 18:54:43 +0800187 except yaml.YAMLError:
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100188 message = "* YAMLVALID: [FAILED]: " + filename
189 result_message_list = []
190 result_message_list.append(message)
191 result_message_list.append("\n\n")
192 exc_type, exc_value, exc_traceback = sys.exc_info()
193 for line in traceback.format_exception_only(exc_type, exc_value):
194 result_message_list.append(' ' + line)
195 publish_result(result_message_list, args)
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100196 args.failed_message_list = args.failed_message_list + result_message_list
Milosz Wasilewskic69b4092017-10-20 14:04:52 +0100197 return 1
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000198 return 0
199
200
201def validate_shell(filename, ignore_options):
202 ignore_string = ""
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000203 if args.shellcheck_ignore is not None:
Chase Qied017432018-12-14 16:56:20 +0800204 # Exclude types of warnings in the following format:
205 # -e CODE1,CODE2..
206 ignore_string = "-e %s" % ",".join(args.shellcheck_ignore)
Milosz Wasilewskiab7645a2016-11-07 10:45:34 +0000207 if len(ignore_string) < 4: # contains only "-e "
208 ignore_string = ""
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000209 cmd = 'shellcheck %s' % ignore_string
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000210 return validate_external(cmd, filename, "SHELLCHECK", args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000211
212
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000213def validate_php(filename, args):
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000214 cmd = 'php -l'
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000215 return validate_external(cmd, filename, "PHPLINT", args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000216
217
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000218def validate_external(cmd, filename, prefix, args):
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000219 final_cmd = "%s %s 2>&1" % (cmd, filename)
220 status, output = subprocess.getstatusoutput(final_cmd)
221 if status == 0:
222 message = '* %s: [PASSED]: %s' % (prefix, filename)
Milosz Wasilewskia85631a2020-03-02 11:21:29 +0000223 publish_result([message], args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000224 else:
225 result_message_list = []
226 result_message_list.append('* %s: [FAILED]: %s' % (prefix, filename))
227 result_message_list.append('* %s: [OUTPUT]:' % prefix)
228 for line in output.splitlines():
229 result_message_list.append(' ' + line)
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000230 publish_result(result_message_list, args)
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100231 args.failed_message_list = args.failed_message_list + result_message_list
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000232 return 1
233 return 0
234
235
236def validate_file(args, path):
Milosz Wasilewskia85631a2020-03-02 11:21:29 +0000237 if args.verbose:
238 print("Validating file: %s" % path)
Chase Qi827203c2019-03-07 16:19:15 +0800239 filetype = magic.from_file(path, mime=True)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000240 exitcode = 0
Chase Qi827203c2019-03-07 16:19:15 +0800241 # libmagic takes yaml as 'text/plain', so use file extension here.
Chase Qi9a5f4cf2019-03-25 11:41:55 +0800242 if path.endswith((".yaml", ".yml")):
Milosz Wasilewskid0b78132016-11-22 19:21:14 +0000243 exitcode = validate_yaml(path, args)
244 if exitcode == 0:
245 # if yaml isn't valid there is no point in checking metadata
Dan Rue8ec540c2018-01-25 14:38:12 -0600246 exitcode = validate_yaml_contents(path, args)
Chase Qi827203c2019-03-07 16:19:15 +0800247 elif run_pycodestyle and filetype == "text/x-python":
Chase Qi30290532018-11-20 18:54:43 +0800248 exitcode = pycodestyle_check(path, args)
Chase Qi827203c2019-03-07 16:19:15 +0800249 elif filetype == "text/x-php":
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000250 exitcode = validate_php(path, args)
Chase Qi9a5f4cf2019-03-25 11:41:55 +0800251 elif path.endswith('.sh') or filetype == "text/x-shellscript":
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000252 exitcode = validate_shell(path, args)
Chase Qi827203c2019-03-07 16:19:15 +0800253 else:
254 publish_result(["* UNKNOWN [SKIPPED]: " + path + " - Unknown file type detected: " + filetype], args)
255 return 0
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000256 return exitcode
257
258
259def run_unit_tests(args, filelist=None):
260 exitcode = 0
261 if filelist is not None:
262 for filename in filelist:
Milosz Wasilewskidb499392016-11-07 19:43:51 +0000263 tmp_exitcode = validate_file(args, filename)
264 if tmp_exitcode != 0:
265 exitcode = 1
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000266 else:
267 for root, dirs, files in os.walk('.'):
268 if not root.startswith("./.git"):
269 for name in files:
Milosz Wasilewskidb499392016-11-07 19:43:51 +0000270 tmp_exitcode = validate_file(
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000271 args,
272 root + "/" + name)
Milosz Wasilewskidb499392016-11-07 19:43:51 +0000273 if tmp_exitcode != 0:
274 exitcode = 1
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000275 return exitcode
276
277
278def main(args):
279 exitcode = 0
280 if args.git_latest:
281 # check if git exists
282 git_status, git_result = subprocess.getstatusoutput(
Chase Qi6b5b4652019-01-11 14:18:28 +0800283 "git diff --name-only HEAD~1")
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000284 if git_status == 0:
285 filelist = git_result.split()
286 exitcode = run_unit_tests(args, filelist)
287 elif len(args.file_path) > 0:
288 exitcode = run_unit_tests(args, [args.file_path])
289 else:
290 exitcode = run_unit_tests(args)
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100291 if not args.verbose:
292 publish_result(None, args)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000293 exit(exitcode)
294
295
296if __name__ == '__main__':
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100297 failed_message_list = []
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000298 parser = argparse.ArgumentParser()
299 parser.add_argument("-p",
Chase Qi30290532018-11-20 18:54:43 +0800300 "--pycodestyle-ignore",
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +0000301 nargs="*",
Milosz Wasilewskiab7645a2016-11-07 10:45:34 +0000302 default=["E501"],
Chase Qi30290532018-11-20 18:54:43 +0800303 help="Space separated list of pycodestyle exclusions",
304 dest="pycodestyle_ignore")
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000305 parser.add_argument("-s",
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +0000306 "--shellcheck-ignore",
307 nargs="*",
308 help="Space separated list of shellcheck exclusions",
309 dest="shellcheck_ignore")
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000310 parser.add_argument("-g",
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +0000311 "--git-latest",
312 action="store_true",
313 default=False,
314 help="If set, the script will try to evaluate files in last git \
315 commit instead of the whole repository",
316 dest="git_latest")
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000317 parser.add_argument("-f",
Milosz Wasilewski7ae041c2016-11-07 11:10:06 +0000318 "--file-path",
319 default="",
320 help="Path to the file that should be checked",
321 dest="file_path")
Milosz Wasilewski65d6f0e2016-11-08 13:54:59 +0000322 parser.add_argument("-r",
323 "--result-file",
324 default="build-error.txt",
325 help="Path to the file that contains results in case of failure",
326 dest="result_file")
Milosz Wasilewskia85631a2020-03-02 11:21:29 +0000327 parser.add_argument("-v",
328 "--verbose",
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100329 action='store_true',
Milosz Wasilewskia85631a2020-03-02 11:21:29 +0000330 default=False,
331 help="Make output more verbose",
332 dest="verbose")
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000333
334 args = parser.parse_args()
Milosz Wasilewski191b4772020-06-30 13:31:17 +0100335 setattr(args, "failed_message_list", failed_message_list)
Milosz Wasilewskidbf52aa2016-11-01 17:51:26 +0000336 main(args)