Milosz Wasilewski | f5ccdbd | 2016-10-25 18:49:20 +0100 | [diff] [blame] | 1 | import os |
| 2 | import subprocess |
| 3 | import yaml |
| 4 | from argparse import ArgumentParser |
Milosz Wasilewski | b504541 | 2016-12-07 11:27:10 +0000 | [diff] [blame] | 5 | from csv import DictWriter |
Milosz Wasilewski | f5ccdbd | 2016-10-25 18:49:20 +0100 | [diff] [blame] | 6 | from jinja2 import Environment, FileSystemLoader |
| 7 | |
| 8 | |
| 9 | def render(obj, template="testplan.html", name=None): |
| 10 | if name is None: |
| 11 | name = template |
| 12 | _env = Environment(loader=FileSystemLoader('templates')) |
| 13 | _template = _env.get_template(template) |
| 14 | _obj = _template.render(obj=obj) |
| 15 | with open("{}".format(name), "wb") as _file: |
| 16 | _file.write(_obj.encode('utf-8')) |
| 17 | |
| 18 | |
| 19 | # get list of repositories and cache them |
| 20 | def repository_list(testplan): |
| 21 | repositories = set() |
| 22 | for req in testplan['requirements']: |
| 23 | if 'tests' in req.keys() and req['tests'] is not None: |
| 24 | if 'manual' in req['tests'].keys() and req['tests']['manual'] is not None: |
| 25 | for test in req['tests']['manual']: |
| 26 | repositories.add(test['repository']) |
| 27 | if 'automated' in req['tests'].keys() and req['tests']['automated'] is not None: |
| 28 | for test in req['tests']['automated']: |
| 29 | repositories.add(test['repository']) |
| 30 | return repositories |
| 31 | |
| 32 | |
| 33 | def clone_repository(repository_url, base_path, ignore=False): |
| 34 | path_suffix = repository_url.rsplit("/", 1)[1] |
| 35 | if path_suffix.endswith(".git"): |
| 36 | path_suffix = path_suffix[:-4] |
| 37 | |
Milosz Wasilewski | b504541 | 2016-12-07 11:27:10 +0000 | [diff] [blame] | 38 | path = os.path.abspath(os.path.join(base_path, path_suffix)) |
Milosz Wasilewski | f5ccdbd | 2016-10-25 18:49:20 +0100 | [diff] [blame] | 39 | if os.path.exists(path) and ignore: |
| 40 | return(repository_url, path) |
| 41 | # git clone repository_url |
| 42 | subprocess.call(['git', 'clone', repository_url, path]) |
| 43 | # return tuple (repository_url, system_path) |
| 44 | return (repository_url, path) |
| 45 | |
| 46 | |
Milosz Wasilewski | b504541 | 2016-12-07 11:27:10 +0000 | [diff] [blame] | 47 | def test_exists(test, repositories, args): |
Milosz Wasilewski | f5ccdbd | 2016-10-25 18:49:20 +0100 | [diff] [blame] | 48 | test_file_path = os.path.join( |
| 49 | repositories[test['repository']], |
| 50 | test['path'] |
| 51 | ) |
| 52 | current_dir = os.getcwd() |
| 53 | print current_dir |
| 54 | os.chdir(repositories[test['repository']]) |
| 55 | if 'revision' in test.keys(): |
| 56 | subprocess.call(['git', 'checkout', test['revision']]) |
| 57 | else: |
| 58 | # if no revision is specified, use current HEAD |
| 59 | output = subprocess.check_output(['git', 'rev-parse', 'HEAD']) |
| 60 | test['revision'] = output |
| 61 | |
| 62 | if not os.path.exists(test_file_path) or not os.path.isfile(test_file_path): |
| 63 | test['missing'] = True |
| 64 | os.chdir(current_dir) |
| 65 | return not test['missing'] |
| 66 | test['missing'] = False |
| 67 | # open the file and render the test |
| 68 | subprocess.call(['git', 'checkout', 'master']) |
| 69 | print current_dir |
| 70 | os.chdir(current_dir) |
| 71 | print os.getcwd() |
| 72 | test_file = open(test_file_path, "r") |
| 73 | test_yaml = yaml.load(test_file.read()) |
| 74 | params_string = "" |
| 75 | if 'parameters' in test.keys(): |
| 76 | params_string = "_".join(["{0}-{1}".format(param_name, param_value).replace("/", "").replace(" ", "") for param_name, param_value in test['parameters'].iteritems()]) |
| 77 | test_yaml['params'].update(test['parameters']) |
| 78 | print params_string |
| 79 | test_name = "{0}_{1}.html".format(test_yaml['metadata']['name'], params_string) |
| 80 | test['filename'] = test_name |
Milosz Wasilewski | b504541 | 2016-12-07 11:27:10 +0000 | [diff] [blame] | 81 | test_path = os.path.join(os.path.abspath(args.output), test_name) |
| 82 | render(test_yaml, template="test.html", name=test_path) |
Milosz Wasilewski | f5ccdbd | 2016-10-25 18:49:20 +0100 | [diff] [blame] | 83 | return not test['missing'] |
| 84 | |
| 85 | |
Milosz Wasilewski | b504541 | 2016-12-07 11:27:10 +0000 | [diff] [blame] | 86 | def add_csv_row(requirement, test, args, manual=False): |
| 87 | fieldnames = [ |
| 88 | "req_name", |
| 89 | "req_owner", |
| 90 | "req_category", |
| 91 | "path", |
| 92 | "repository", |
| 93 | "revision", |
| 94 | "parameters", |
| 95 | "mandatory", |
| 96 | "kind", |
| 97 | ] |
| 98 | csv_file_path = os.path.join(os.path.abspath(args.output), args.csv_name) |
| 99 | has_header = False |
| 100 | if os.path.isfile(csv_file_path): |
| 101 | has_header = True |
| 102 | with open(csv_file_path, "ab+") as csv_file: |
| 103 | csvdict = DictWriter(csv_file, fieldnames=fieldnames) |
| 104 | if not has_header: |
| 105 | csvdict.writeheader() |
| 106 | csvdict.writerow( |
| 107 | { |
| 108 | "req_name": requirement.get('name'), |
| 109 | "req_owner": requirement.get('owner'), |
| 110 | "req_category": requirement.get('category'), |
| 111 | "path": test.get('path'), |
| 112 | "repository": test.get('repository'), |
| 113 | "revision": test.get('revision'), |
| 114 | "parameters": test.get('parameters'), |
| 115 | "mandatory": test.get('mandatory'), |
| 116 | "kind": "manual" if manual else "automated", |
| 117 | } |
| 118 | ) |
| 119 | |
| 120 | |
| 121 | def check_coverage(requirement, repositories, args): |
Milosz Wasilewski | f5ccdbd | 2016-10-25 18:49:20 +0100 | [diff] [blame] | 122 | requirement['covered'] = False |
Milosz Wasilewski | b504541 | 2016-12-07 11:27:10 +0000 | [diff] [blame] | 123 | if 'tests' not in requirement.keys() or requirement['tests'] is None: |
Milosz Wasilewski | f5ccdbd | 2016-10-25 18:49:20 +0100 | [diff] [blame] | 124 | return |
| 125 | if 'manual' in requirement['tests'].keys() and requirement['tests']['manual'] is not None: |
| 126 | for test in requirement['tests']['manual']: |
Milosz Wasilewski | b504541 | 2016-12-07 11:27:10 +0000 | [diff] [blame] | 127 | if test_exists(test, repositories, args): |
Milosz Wasilewski | f5ccdbd | 2016-10-25 18:49:20 +0100 | [diff] [blame] | 128 | requirement['covered'] = True |
Milosz Wasilewski | b504541 | 2016-12-07 11:27:10 +0000 | [diff] [blame] | 129 | if args.csv_name: |
| 130 | add_csv_row(requirement, test, args, True) |
Milosz Wasilewski | f5ccdbd | 2016-10-25 18:49:20 +0100 | [diff] [blame] | 131 | if 'automated' in requirement['tests'].keys() and requirement['tests']['automated'] is not None: |
| 132 | for test in requirement['tests']['automated']: |
Milosz Wasilewski | b504541 | 2016-12-07 11:27:10 +0000 | [diff] [blame] | 133 | if test_exists(test, repositories, args): |
Milosz Wasilewski | f5ccdbd | 2016-10-25 18:49:20 +0100 | [diff] [blame] | 134 | requirement['covered'] = True |
Milosz Wasilewski | b504541 | 2016-12-07 11:27:10 +0000 | [diff] [blame] | 135 | if args.csv_name: |
| 136 | add_csv_row(requirement, test, args) |
Milosz Wasilewski | f5ccdbd | 2016-10-25 18:49:20 +0100 | [diff] [blame] | 137 | |
| 138 | |
| 139 | def main(): |
| 140 | parser = ArgumentParser() |
| 141 | parser.add_argument("-f", |
Milosz Wasilewski | b504541 | 2016-12-07 11:27:10 +0000 | [diff] [blame] | 142 | "--file", |
| 143 | dest="testplan_list", |
| 144 | required=True, |
| 145 | nargs="+", |
| 146 | help="Test plan file to be used") |
Milosz Wasilewski | f5ccdbd | 2016-10-25 18:49:20 +0100 | [diff] [blame] | 147 | parser.add_argument("-r", |
Milosz Wasilewski | b504541 | 2016-12-07 11:27:10 +0000 | [diff] [blame] | 148 | "--repositories", |
| 149 | dest="repository_path", |
| 150 | default="repositories", |
| 151 | help="Test plan file to be used") |
| 152 | parser.add_argument("-o", |
| 153 | "--output", |
| 154 | dest="output", |
| 155 | default="output", |
| 156 | help="Destination directory for generated files") |
Milosz Wasilewski | f5ccdbd | 2016-10-25 18:49:20 +0100 | [diff] [blame] | 157 | parser.add_argument("-i", |
Milosz Wasilewski | b504541 | 2016-12-07 11:27:10 +0000 | [diff] [blame] | 158 | "--ignore-clone", |
| 159 | dest="ignore_clone", |
| 160 | action="store_true", |
| 161 | default=False, |
| 162 | help="Ignore cloning repositories and use previously cloned") |
| 163 | parser.add_argument("-c", |
| 164 | "--csv", |
| 165 | dest="csv_name", |
| 166 | required=False, |
| 167 | help="Name of CSV to store overall list of requirements and test. If name is absent, the file will not be generated") |
Milosz Wasilewski | f5ccdbd | 2016-10-25 18:49:20 +0100 | [diff] [blame] | 168 | |
| 169 | args = parser.parse_args() |
Milosz Wasilewski | b504541 | 2016-12-07 11:27:10 +0000 | [diff] [blame] | 170 | if not os.path.exists(os.path.abspath(args.output)): |
| 171 | os.makedirs(os.path.abspath(args.output), 0755) |
| 172 | for testplan in args.testplan_list: |
| 173 | if os.path.exists(testplan) and os.path.isfile(testplan): |
| 174 | testplan_file = open(testplan, "r") |
| 175 | tp_obj = yaml.load(testplan_file.read()) |
| 176 | repo_list = repository_list(tp_obj) |
| 177 | repositories = {} |
| 178 | for repo in repo_list: |
| 179 | repo_url, repo_path = clone_repository(repo, args.repository_path, args.ignore_clone) |
| 180 | repositories.update({repo_url: repo_path}) |
| 181 | # ToDo: check test plan structure |
| 182 | for requirement in tp_obj['requirements']: |
| 183 | check_coverage(requirement, repositories, args) |
| 184 | tp_name = tp_obj['metadata']['name'] + ".html" |
| 185 | tp_file_name = os.path.join(os.path.abspath(args.output), tp_name) |
| 186 | render(tp_obj, name=tp_file_name) |
| 187 | testplan_file.close() |
Milosz Wasilewski | f5ccdbd | 2016-10-25 18:49:20 +0100 | [diff] [blame] | 188 | # go through requiremets and for each test: |
| 189 | # - if file exists render test as separate html file |
| 190 | # - if file is missing, indicate missing test (red) |
| 191 | # render test plan with links to test files |
| 192 | # add option to render as single file (for pdf generation) |
| 193 | |
| 194 | if __name__ == "__main__": |
| 195 | main() |