blob: d8b82198dd889ce579af1398bb5839b081fca29e [file] [log] [blame]
Milosz Wasilewski68279b22018-02-21 12:41:02 +00001import collections
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +01002import os
3import subprocess
4import yaml
5from argparse import ArgumentParser
Milosz Wasilewskib5045412016-12-07 11:27:10 +00006from csv import DictWriter
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +01007from jinja2 import Environment, FileSystemLoader
8
9
Milosz Wasilewski68279b22018-02-21 12:41:02 +000010class PrependOrderedDict(collections.OrderedDict):
11
12 def prepend(self, key, value, dict_setitem=dict.__setitem__):
13
14 root = self._OrderedDict__root
15 first = root[1]
16
17 if key in self:
18 link = self._OrderedDict__map[key]
19 link_prev, link_next, _ = link
20 link_prev[1] = link_next
21 link_next[0] = link_prev
22 link[0] = root
23 link[1] = first
24 root[1] = first[0] = link
25 else:
26 root[1] = first[0] = self._OrderedDict__map[key] = [root, first, key]
27 dict_setitem(self, key, value)
28
29
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +010030def render(obj, template="testplan.html", name=None):
31 if name is None:
32 name = template
Milosz Wasilewskif761ab92018-02-13 11:21:18 +000033 templates_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "templates")
34 _env = Environment(loader=FileSystemLoader(templates_dir))
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +010035 _template = _env.get_template(template)
36 _obj = _template.render(obj=obj)
37 with open("{}".format(name), "wb") as _file:
38 _file.write(_obj.encode('utf-8'))
39
40
41# get list of repositories and cache them
42def repository_list(testplan):
43 repositories = set()
Milosz Wasilewskid5e1dd92018-02-12 12:08:44 +000044 tp_version = testplan['metadata']['format']
45 if tp_version == "Linaro Test Plan v2":
46 if 'manual' in testplan['tests'].keys() and testplan['tests']['manual'] is not None:
47 for test in testplan['tests']['manual']:
48 repositories.add(test['repository'])
49
50 if 'automated' in testplan['tests'].keys() and testplan['tests']['automated'] is not None:
51 for test in testplan['tests']['automated']:
52 repositories.add(test['repository'])
53 if tp_version == "Linaro Test Plan v1":
54 for req in testplan['requirements']:
55 if 'tests' in req.keys() and req['tests'] is not None:
56 if 'manual' in req['tests'].keys() and req['tests']['manual'] is not None:
57 for test in req['tests']['manual']:
58 repositories.add(test['repository'])
59 if 'automated' in req['tests'].keys() and req['tests']['automated'] is not None:
60 for test in req['tests']['automated']:
61 repositories.add(test['repository'])
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +010062 return repositories
63
64
65def clone_repository(repository_url, base_path, ignore=False):
66 path_suffix = repository_url.rsplit("/", 1)[1]
67 if path_suffix.endswith(".git"):
68 path_suffix = path_suffix[:-4]
69
Milosz Wasilewskib5045412016-12-07 11:27:10 +000070 path = os.path.abspath(os.path.join(base_path, path_suffix))
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +010071 if os.path.exists(path) and ignore:
72 return(repository_url, path)
73 # git clone repository_url
74 subprocess.call(['git', 'clone', repository_url, path])
75 # return tuple (repository_url, system_path)
76 return (repository_url, path)
77
78
Milosz Wasilewskib5045412016-12-07 11:27:10 +000079def test_exists(test, repositories, args):
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +010080 test_file_path = os.path.join(
81 repositories[test['repository']],
82 test['path']
83 )
84 current_dir = os.getcwd()
85 print current_dir
86 os.chdir(repositories[test['repository']])
87 if 'revision' in test.keys():
88 subprocess.call(['git', 'checkout', test['revision']])
Milosz Wasilewski9a83b8e2020-07-10 11:43:33 +010089 elif 'branch' in test.keys():
90 subprocess.call(['git', 'checkout', test['branch']])
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +010091 else:
92 # if no revision is specified, use current HEAD
93 output = subprocess.check_output(['git', 'rev-parse', 'HEAD'])
94 test['revision'] = output
95
96 if not os.path.exists(test_file_path) or not os.path.isfile(test_file_path):
97 test['missing'] = True
98 os.chdir(current_dir)
99 return not test['missing']
100 test['missing'] = False
101 # open the file and render the test
102 subprocess.call(['git', 'checkout', 'master'])
103 print current_dir
104 os.chdir(current_dir)
105 print os.getcwd()
106 test_file = open(test_file_path, "r")
107 test_yaml = yaml.load(test_file.read())
108 params_string = ""
109 if 'parameters' in test.keys():
110 params_string = "_".join(["{0}-{1}".format(param_name, param_value).replace("/", "").replace(" ", "") for param_name, param_value in test['parameters'].iteritems()])
111 test_yaml['params'].update(test['parameters'])
Milosz Wasilewski807c0ea2018-02-13 19:14:01 +0000112 if args.single_output:
113 # update parameters in test
114 if 'params' in test_yaml.keys():
115 for param_name, param_value in test_yaml['params'].iteritems():
116 if param_name not in test['parameters'].keys():
117 test['parameters'].update({param_name: param_value})
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100118 print params_string
119 test_name = "{0}_{1}.html".format(test_yaml['metadata']['name'], params_string)
Milosz Wasilewski807c0ea2018-02-13 19:14:01 +0000120 if not args.single_output:
121 test['filename'] = test_name
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000122 test_path = os.path.join(os.path.abspath(args.output), test_name)
Milosz Wasilewski807c0ea2018-02-13 19:14:01 +0000123 if args.single_output:
124 # update test plan object
125 test.update(test_yaml['run'])
Milosz Wasilewski68279b22018-02-21 12:41:02 +0000126 # prepend in reversed order so 'name' is on top
127 test.prepend("os", test_yaml['metadata']['os'])
128 test.prepend("scope", test_yaml['metadata']['scope'])
129 test.prepend("description", test_yaml['metadata']['description'])
130 test.prepend("name", test_yaml['metadata']['name'])
Milosz Wasilewski807c0ea2018-02-13 19:14:01 +0000131 else:
132 render(test_yaml, template="test.html", name=test_path)
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100133 return not test['missing']
134
135
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000136def add_csv_row(requirement, test, args, manual=False):
137 fieldnames = [
138 "req_name",
139 "req_owner",
140 "req_category",
141 "path",
142 "repository",
143 "revision",
144 "parameters",
145 "mandatory",
146 "kind",
147 ]
148 csv_file_path = os.path.join(os.path.abspath(args.output), args.csv_name)
149 has_header = False
150 if os.path.isfile(csv_file_path):
151 has_header = True
152 with open(csv_file_path, "ab+") as csv_file:
153 csvdict = DictWriter(csv_file, fieldnames=fieldnames)
154 if not has_header:
155 csvdict.writeheader()
156 csvdict.writerow(
157 {
158 "req_name": requirement.get('name'),
159 "req_owner": requirement.get('owner'),
160 "req_category": requirement.get('category'),
161 "path": test.get('path'),
162 "repository": test.get('repository'),
163 "revision": test.get('revision'),
164 "parameters": test.get('parameters'),
165 "mandatory": test.get('mandatory'),
166 "kind": "manual" if manual else "automated",
167 }
168 )
169
170
171def check_coverage(requirement, repositories, args):
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100172 requirement['covered'] = False
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000173 if 'tests' not in requirement.keys() or requirement['tests'] is None:
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100174 return
175 if 'manual' in requirement['tests'].keys() and requirement['tests']['manual'] is not None:
176 for test in requirement['tests']['manual']:
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000177 if test_exists(test, repositories, args):
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100178 requirement['covered'] = True
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000179 if args.csv_name:
180 add_csv_row(requirement, test, args, True)
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100181 if 'automated' in requirement['tests'].keys() and requirement['tests']['automated'] is not None:
182 for test in requirement['tests']['automated']:
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000183 if test_exists(test, repositories, args):
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100184 requirement['covered'] = True
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000185 if args.csv_name:
186 add_csv_row(requirement, test, args)
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100187
188
Milosz Wasilewski68279b22018-02-21 12:41:02 +0000189def dict_representer(dumper, data):
190 return dumper.represent_dict(data.iteritems())
191
192
193def dict_constructor(loader, node):
194 return PrependOrderedDict(loader.construct_pairs(node))
195
196
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100197def main():
198 parser = ArgumentParser()
199 parser.add_argument("-f",
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000200 "--file",
201 dest="testplan_list",
202 required=True,
203 nargs="+",
204 help="Test plan file to be used")
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100205 parser.add_argument("-r",
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000206 "--repositories",
207 dest="repository_path",
208 default="repositories",
209 help="Test plan file to be used")
210 parser.add_argument("-o",
211 "--output",
212 dest="output",
213 default="output",
214 help="Destination directory for generated files")
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100215 parser.add_argument("-i",
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000216 "--ignore-clone",
217 dest="ignore_clone",
218 action="store_true",
219 default=False,
220 help="Ignore cloning repositories and use previously cloned")
Milosz Wasilewski807c0ea2018-02-13 19:14:01 +0000221 parser.add_argument("-s",
222 "--single-file-output",
223 dest="single_output",
224 action="store_true",
225 default=False,
226 help="""Render test plan into single HTML file. This option ignores
227 any metadata that is available in test cases""")
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000228 parser.add_argument("-c",
229 "--csv",
230 dest="csv_name",
231 required=False,
232 help="Name of CSV to store overall list of requirements and test. If name is absent, the file will not be generated")
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100233
Milosz Wasilewski68279b22018-02-21 12:41:02 +0000234 _mapping_tag = yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG
235 yaml.add_representer(PrependOrderedDict, dict_representer)
236 yaml.add_constructor(_mapping_tag, dict_constructor)
237
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100238 args = parser.parse_args()
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000239 if not os.path.exists(os.path.abspath(args.output)):
240 os.makedirs(os.path.abspath(args.output), 0755)
241 for testplan in args.testplan_list:
242 if os.path.exists(testplan) and os.path.isfile(testplan):
243 testplan_file = open(testplan, "r")
244 tp_obj = yaml.load(testplan_file.read())
245 repo_list = repository_list(tp_obj)
246 repositories = {}
247 for repo in repo_list:
248 repo_url, repo_path = clone_repository(repo, args.repository_path, args.ignore_clone)
249 repositories.update({repo_url: repo_path})
250 # ToDo: check test plan structure
Milosz Wasilewskid5e1dd92018-02-12 12:08:44 +0000251
252 tp_version = tp_obj['metadata']['format']
253 if tp_version == "Linaro Test Plan v1":
254 for requirement in tp_obj['requirements']:
255 check_coverage(requirement, repositories, args)
256 if tp_version == "Linaro Test Plan v2":
257 if 'manual' in tp_obj['tests'].keys() and tp_obj['tests']['manual'] is not None:
258 for test in tp_obj['tests']['manual']:
259 test_exists(test, repositories, args)
260 if 'automated' in tp_obj['tests'].keys() and tp_obj['tests']['automated'] is not None:
261 for test in tp_obj['tests']['automated']:
262 test_exists(test, repositories, args)
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000263 tp_name = tp_obj['metadata']['name'] + ".html"
264 tp_file_name = os.path.join(os.path.abspath(args.output), tp_name)
Milosz Wasilewskid5e1dd92018-02-12 12:08:44 +0000265 if tp_version == "Linaro Test Plan v1":
266 render(tp_obj, name=tp_file_name)
267 if tp_version == "Linaro Test Plan v2":
268 render(tp_obj, name=tp_file_name, template="testplan_v2.html")
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000269 testplan_file.close()
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100270# go through requiremets and for each test:
271# - if file exists render test as separate html file
272# - if file is missing, indicate missing test (red)
273# render test plan with links to test files
274# add option to render as single file (for pdf generation)
275
Milosz Wasilewski60152062020-03-02 18:53:29 +0000276
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100277if __name__ == "__main__":
278 main()