diff options
Diffstat (limited to 'plans')
-rw-r--r-- | plans/templates/_test_details.html | 22 | ||||
-rw-r--r-- | plans/templates/test.html | 96 | ||||
-rw-r--r-- | plans/templates/testplan.html | 73 | ||||
-rw-r--r-- | plans/testplan2html.py | 139 |
4 files changed, 330 insertions, 0 deletions
diff --git a/plans/templates/_test_details.html b/plans/templates/_test_details.html new file mode 100644 index 0000000..ed7d41d --- /dev/null +++ b/plans/templates/_test_details.html @@ -0,0 +1,22 @@ +<table class="table table-striped"> + {% if 'filename' in test and test.filename != "" %} + <h4><a href="{{ test.filename }}">details</a></h4> + {% endif %} + <tbody> + {% for key,value in test.items() %} + {% if key != 'missing' and key != 'filename' %} + <tr> + <td>{{ key }}</td> + <td> + {% if value is mapping %} + {% for subkey,subvalue in value.items() %} + <strong>{{ subkey }}</strong>: {{ subvalue }}<br/> + {% endfor %} + {% else %} + {{ value }} + {% endif %} + </tr> + {% endif %} + {% endfor %} + </tbody> +</table> diff --git a/plans/templates/test.html b/plans/templates/test.html new file mode 100644 index 0000000..f3d57b7 --- /dev/null +++ b/plans/templates/test.html @@ -0,0 +1,96 @@ +<!DOCTYPE html> +<html lang=en> + <head> + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> + <!-- Optional theme --> + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous"> + <!-- Latest compiled and minified JavaScript --> + <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> + + </head> + <body> + <div class="container"> + <h1>{{ obj.metadata.name }}</h1> + <h3>Metadata</h3> + <table class="table table-striped"> + <tbody> + {% for key,value in obj.metadata.items() %} + {% if key != 'name' %} + <tr> + <td>{{ key }}</td> + <td> + {% if value is sequence and value is not string %} + <ul> + {% for item in value %} + <li>{{ item }} + {% endfor %} + </ul> + {% else %} + {{ value }} + {% endif %} + </td> + </tr> + {% endif %} + {% endfor %} + </tbody> + </table> + {% if 'params' in obj %} + <h3>Parameters</h3> + <table class="table table-striped"> + <tbody> + {% for key,value in obj.params.items() %} + {% if key != 'name' %} + <tr> + <td>{{ key }}</td> + <td> + {% if value is mapping %} + {% for subkey,subvalue in value.items() %} + <strong>{{ subkey }}</strong>: {{ subvalue }}<br/> + {% endfor %} + {% else %} + {{ value }} + {% endif %} + </td> + </tr> + {% endif %} + {% endfor %} + </tbody> + </table> + {% endif %} + {% if 'install' in obj %} + <h3>Installation</h3> + <table class="table table-striped"> + <tbody> + {% for value in obj.install.steps %} + <tr> + <td>{{ value }}</td> + </tr> + {% endfor %} + </tbody> + </table> + {% endif %} + <h3>Run</h3> + <table class="table table-striped"> + <tbody> + {% for value in obj.run.steps %} + <tr> + <td>{{ value }}</td> + </tr> + {% endfor %} + </tbody> + </table> + {% if 'expected' in obj.run %} + <h3>Expected</h3> + <table class="table table-striped"> + <tbody> + {% for value in obj.run.expected %} + <tr> + <td>{{ value }}</td> + </tr> + {% endfor %} + </tbody> + </table> + {% endif %} + </div> + </body> +</html> diff --git a/plans/templates/testplan.html b/plans/templates/testplan.html new file mode 100644 index 0000000..6551b2c --- /dev/null +++ b/plans/templates/testplan.html @@ -0,0 +1,73 @@ +<!DOCTYPE html> +<html lang=en> + <head> + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> + <!-- Optional theme --> + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous"> + <!-- Latest compiled and minified JavaScript --> + <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> + </head> + <body> + <div class="container"> + <div id="metadata"> + <h1>Metadata</h1> + <table class="table table-striped"> + <thead> + <tr> + <th>Key</th> + <th>Value</th> + </tr> + </thead> + <tbody> + {% for key,value in obj.metadata.items() %} + <tr> + <td>{{ key }}</td> + <td> + {% if value is sequence and value is not string %} + <ul> + {% for item in value %} + <li>{{ item }} + {% endfor %} + </ul> + {% else %} + {{ value }} + {% endif %} + </td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + <div id="requirements"> + <h1>Requirements</h1> + {% for req in obj.requirements %} + {% set outer_loop = loop %} + <div {% if req.covered %}class="covered"{% else %}class="alert alert-danger"{% endif %}> + <h2>{{ loop.index}} {{ req.name }}</h2> + <h4>Owner: {{ req.owner }}</h4> + {% if req.tests %} + {% if req.tests.manual is defined and req.tests.manual is not none %} + <h3>Manual tests</h3> + {% for test in req.tests.manual %} + <div {% if test.missing %}class="alert alert-danger"{% endif %}> + <h5>{{ outer_loop.index }}.{{ loop.index }}</h5> + {% include "_test_details.html" %} + </div> + {% endfor %} + {% endif %} + {% if req.tests.automated is defined and req.tests.automated is not none %} + <h3>Automated tests</h3> + {% for test in req.tests.automated %} + <div {% if test.missing %}class="alert alert-danger"{% endif %}> + <h5>{{ outer_loop.index }}.{{ loop.index }}</h5> + {% include "_test_details.html" %} + </div> + {% endfor %} + {% endif %} + {% endif %} + </div> + {% endfor %} + </div> + </div> + </body> +</html> diff --git a/plans/testplan2html.py b/plans/testplan2html.py new file mode 100644 index 0000000..ab9b0bb --- /dev/null +++ b/plans/testplan2html.py @@ -0,0 +1,139 @@ +import os +import subprocess +import yaml +from argparse import ArgumentParser +from jinja2 import Environment, FileSystemLoader + + +def render(obj, template="testplan.html", name=None): + if name is None: + name = template + _env = Environment(loader=FileSystemLoader('templates')) + _template = _env.get_template(template) + _obj = _template.render(obj=obj) + with open("{}".format(name), "wb") as _file: + _file.write(_obj.encode('utf-8')) + + +# get list of repositories and cache them +def repository_list(testplan): + repositories = set() + for req in testplan['requirements']: + if 'tests' in req.keys() and req['tests'] is not None: + if 'manual' in req['tests'].keys() and req['tests']['manual'] is not None: + for test in req['tests']['manual']: + repositories.add(test['repository']) + if 'automated' in req['tests'].keys() and req['tests']['automated'] is not None: + for test in req['tests']['automated']: + repositories.add(test['repository']) + return repositories + + +def clone_repository(repository_url, base_path, ignore=False): + path_suffix = repository_url.rsplit("/", 1)[1] + if path_suffix.endswith(".git"): + path_suffix = path_suffix[:-4] + + path = os.path.abspath(os.path.join(base_path, path_suffix)) + if os.path.exists(path) and ignore: + return(repository_url, path) + # git clone repository_url + subprocess.call(['git', 'clone', repository_url, path]) + # return tuple (repository_url, system_path) + return (repository_url, path) + + +def test_exists(test, repositories): + test_file_path = os.path.join( + repositories[test['repository']], + test['path'] + ) + current_dir = os.getcwd() + print current_dir + os.chdir(repositories[test['repository']]) + if 'revision' in test.keys(): + subprocess.call(['git', 'checkout', test['revision']]) + else: + # if no revision is specified, use current HEAD + output = subprocess.check_output(['git', 'rev-parse', 'HEAD']) + test['revision'] = output + + if not os.path.exists(test_file_path) or not os.path.isfile(test_file_path): + test['missing'] = True + os.chdir(current_dir) + return not test['missing'] + test['missing'] = False + # open the file and render the test + subprocess.call(['git', 'checkout', 'master']) + print current_dir + os.chdir(current_dir) + print os.getcwd() + test_file = open(test_file_path, "r") + test_yaml = yaml.load(test_file.read()) + params_string = "" + if 'parameters' in test.keys(): + params_string = "_".join(["{0}-{1}".format(param_name, param_value).replace("/", "").replace(" ", "") for param_name, param_value in test['parameters'].iteritems()]) + test_yaml['params'].update(test['parameters']) + print params_string + test_name = "{0}_{1}.html".format(test_yaml['metadata']['name'], params_string) + test['filename'] = test_name + render(test_yaml, template="test.html", name=test_name) + return not test['missing'] + + +def check_coverage(requirement, repositories): + requirement['covered'] = False + if not 'tests' in requirement.keys() or requirement['tests'] is None: + return + if 'manual' in requirement['tests'].keys() and requirement['tests']['manual'] is not None: + for test in requirement['tests']['manual']: + if test_exists(test, repositories) : + requirement['covered'] = True + if 'automated' in requirement['tests'].keys() and requirement['tests']['automated'] is not None: + for test in requirement['tests']['automated']: + if test_exists(test, repositories): + requirement['covered'] = True + + +def main(): + parser = ArgumentParser() + parser.add_argument("-f", + "--file", + dest="testplan", + required=True, + help="Test plan file to be used") + parser.add_argument("-r", + "--repositories", + dest="repository_path", + default="repositories", + help="Test plan file to be used") + parser.add_argument("-i", + "--ignore-clone", + dest="ignore_clone", + action="store_true", + default=False, + help="Ignore cloning repositories and use previously cloned") + + args = parser.parse_args() + print args.ignore_clone + if os.path.exists(args.testplan) and os.path.isfile(args.testplan): + testplan = open(args.testplan, "r") + tp_obj = yaml.load(testplan.read()) + repo_list = repository_list(tp_obj) + repositories = {} + for repo in repo_list: + repo_url, repo_path = clone_repository(repo, args.repository_path, args.ignore_clone) + repositories.update({repo_url: repo_path}) + # ToDo: check test plan structure + for requirement in tp_obj['requirements']: + check_coverage(requirement, repositories) + render(tp_obj) + testplan.close() +# go through requiremets and for each test: +# - if file exists render test as separate html file +# - if file is missing, indicate missing test (red) +# render test plan with links to test files +# add option to render as single file (for pdf generation) + +if __name__ == "__main__": + main() |