blob: b46349263b13b40dd0df32123db679584d8e457b [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']])
89 else:
90 # if no revision is specified, use current HEAD
91 output = subprocess.check_output(['git', 'rev-parse', 'HEAD'])
92 test['revision'] = output
93
94 if not os.path.exists(test_file_path) or not os.path.isfile(test_file_path):
95 test['missing'] = True
96 os.chdir(current_dir)
97 return not test['missing']
98 test['missing'] = False
99 # open the file and render the test
100 subprocess.call(['git', 'checkout', 'master'])
101 print current_dir
102 os.chdir(current_dir)
103 print os.getcwd()
104 test_file = open(test_file_path, "r")
105 test_yaml = yaml.load(test_file.read())
106 params_string = ""
107 if 'parameters' in test.keys():
108 params_string = "_".join(["{0}-{1}".format(param_name, param_value).replace("/", "").replace(" ", "") for param_name, param_value in test['parameters'].iteritems()])
109 test_yaml['params'].update(test['parameters'])
Milosz Wasilewski807c0ea2018-02-13 19:14:01 +0000110 if args.single_output:
111 # update parameters in test
112 if 'params' in test_yaml.keys():
113 for param_name, param_value in test_yaml['params'].iteritems():
114 if param_name not in test['parameters'].keys():
115 test['parameters'].update({param_name: param_value})
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100116 print params_string
117 test_name = "{0}_{1}.html".format(test_yaml['metadata']['name'], params_string)
Milosz Wasilewski807c0ea2018-02-13 19:14:01 +0000118 if not args.single_output:
119 test['filename'] = test_name
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000120 test_path = os.path.join(os.path.abspath(args.output), test_name)
Milosz Wasilewski807c0ea2018-02-13 19:14:01 +0000121 if args.single_output:
122 # update test plan object
123 test.update(test_yaml['run'])
Milosz Wasilewski68279b22018-02-21 12:41:02 +0000124 # prepend in reversed order so 'name' is on top
125 test.prepend("os", test_yaml['metadata']['os'])
126 test.prepend("scope", test_yaml['metadata']['scope'])
127 test.prepend("description", test_yaml['metadata']['description'])
128 test.prepend("name", test_yaml['metadata']['name'])
Milosz Wasilewski807c0ea2018-02-13 19:14:01 +0000129 else:
130 render(test_yaml, template="test.html", name=test_path)
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100131 return not test['missing']
132
133
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000134def add_csv_row(requirement, test, args, manual=False):
135 fieldnames = [
136 "req_name",
137 "req_owner",
138 "req_category",
139 "path",
140 "repository",
141 "revision",
142 "parameters",
143 "mandatory",
144 "kind",
145 ]
146 csv_file_path = os.path.join(os.path.abspath(args.output), args.csv_name)
147 has_header = False
148 if os.path.isfile(csv_file_path):
149 has_header = True
150 with open(csv_file_path, "ab+") as csv_file:
151 csvdict = DictWriter(csv_file, fieldnames=fieldnames)
152 if not has_header:
153 csvdict.writeheader()
154 csvdict.writerow(
155 {
156 "req_name": requirement.get('name'),
157 "req_owner": requirement.get('owner'),
158 "req_category": requirement.get('category'),
159 "path": test.get('path'),
160 "repository": test.get('repository'),
161 "revision": test.get('revision'),
162 "parameters": test.get('parameters'),
163 "mandatory": test.get('mandatory'),
164 "kind": "manual" if manual else "automated",
165 }
166 )
167
168
169def check_coverage(requirement, repositories, args):
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100170 requirement['covered'] = False
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000171 if 'tests' not in requirement.keys() or requirement['tests'] is None:
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100172 return
173 if 'manual' in requirement['tests'].keys() and requirement['tests']['manual'] is not None:
174 for test in requirement['tests']['manual']:
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000175 if test_exists(test, repositories, args):
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100176 requirement['covered'] = True
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000177 if args.csv_name:
178 add_csv_row(requirement, test, args, True)
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100179 if 'automated' in requirement['tests'].keys() and requirement['tests']['automated'] is not None:
180 for test in requirement['tests']['automated']:
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000181 if test_exists(test, repositories, args):
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100182 requirement['covered'] = True
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000183 if args.csv_name:
184 add_csv_row(requirement, test, args)
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100185
186
Milosz Wasilewski68279b22018-02-21 12:41:02 +0000187def dict_representer(dumper, data):
188 return dumper.represent_dict(data.iteritems())
189
190
191def dict_constructor(loader, node):
192 return PrependOrderedDict(loader.construct_pairs(node))
193
194
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100195def main():
196 parser = ArgumentParser()
197 parser.add_argument("-f",
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000198 "--file",
199 dest="testplan_list",
200 required=True,
201 nargs="+",
202 help="Test plan file to be used")
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100203 parser.add_argument("-r",
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000204 "--repositories",
205 dest="repository_path",
206 default="repositories",
207 help="Test plan file to be used")
208 parser.add_argument("-o",
209 "--output",
210 dest="output",
211 default="output",
212 help="Destination directory for generated files")
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100213 parser.add_argument("-i",
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000214 "--ignore-clone",
215 dest="ignore_clone",
216 action="store_true",
217 default=False,
218 help="Ignore cloning repositories and use previously cloned")
Milosz Wasilewski807c0ea2018-02-13 19:14:01 +0000219 parser.add_argument("-s",
220 "--single-file-output",
221 dest="single_output",
222 action="store_true",
223 default=False,
224 help="""Render test plan into single HTML file. This option ignores
225 any metadata that is available in test cases""")
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000226 parser.add_argument("-c",
227 "--csv",
228 dest="csv_name",
229 required=False,
230 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 +0100231
Milosz Wasilewski68279b22018-02-21 12:41:02 +0000232 _mapping_tag = yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG
233 yaml.add_representer(PrependOrderedDict, dict_representer)
234 yaml.add_constructor(_mapping_tag, dict_constructor)
235
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100236 args = parser.parse_args()
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000237 if not os.path.exists(os.path.abspath(args.output)):
238 os.makedirs(os.path.abspath(args.output), 0755)
239 for testplan in args.testplan_list:
240 if os.path.exists(testplan) and os.path.isfile(testplan):
241 testplan_file = open(testplan, "r")
242 tp_obj = yaml.load(testplan_file.read())
243 repo_list = repository_list(tp_obj)
244 repositories = {}
245 for repo in repo_list:
246 repo_url, repo_path = clone_repository(repo, args.repository_path, args.ignore_clone)
247 repositories.update({repo_url: repo_path})
248 # ToDo: check test plan structure
Milosz Wasilewskid5e1dd92018-02-12 12:08:44 +0000249
250 tp_version = tp_obj['metadata']['format']
251 if tp_version == "Linaro Test Plan v1":
252 for requirement in tp_obj['requirements']:
253 check_coverage(requirement, repositories, args)
254 if tp_version == "Linaro Test Plan v2":
255 if 'manual' in tp_obj['tests'].keys() and tp_obj['tests']['manual'] is not None:
256 for test in tp_obj['tests']['manual']:
257 test_exists(test, repositories, args)
258 if 'automated' in tp_obj['tests'].keys() and tp_obj['tests']['automated'] is not None:
259 for test in tp_obj['tests']['automated']:
260 test_exists(test, repositories, args)
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000261 tp_name = tp_obj['metadata']['name'] + ".html"
262 tp_file_name = os.path.join(os.path.abspath(args.output), tp_name)
Milosz Wasilewskid5e1dd92018-02-12 12:08:44 +0000263 if tp_version == "Linaro Test Plan v1":
264 render(tp_obj, name=tp_file_name)
265 if tp_version == "Linaro Test Plan v2":
266 render(tp_obj, name=tp_file_name, template="testplan_v2.html")
Milosz Wasilewskib5045412016-12-07 11:27:10 +0000267 testplan_file.close()
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100268# go through requiremets and for each test:
269# - if file exists render test as separate html file
270# - if file is missing, indicate missing test (red)
271# render test plan with links to test files
272# add option to render as single file (for pdf generation)
273
Milosz Wasilewski60152062020-03-02 18:53:29 +0000274
Milosz Wasilewskif5ccdbd2016-10-25 18:49:20 +0100275if __name__ == "__main__":
276 main()