summaryrefslogtreecommitdiff
path: root/plans
diff options
context:
space:
mode:
authorMilosz Wasilewski <milosz.wasilewski@linaro.org>2016-10-25 18:49:20 +0100
committerMilosz Wasilewski <milosz.wasilewski@linaro.org>2016-11-01 11:46:01 +0000
commitf5ccdbdeccd010b47840cd90f2f8f88c59f4c1da (patch)
tree6812036ab71b0c40300c8b9d924a2211da320b43 /plans
parentdf73087277c1062ae6d1d7f781d5a5a386681ced (diff)
plans: added test plan rendering script
Change-Id: I3765f688be40dc6fe0a91a9ed6ed72cc3dfd88db Signed-off-by: Milosz Wasilewski <milosz.wasilewski@linaro.org>
Diffstat (limited to 'plans')
-rw-r--r--plans/templates/_test_details.html22
-rw-r--r--plans/templates/test.html96
-rw-r--r--plans/templates/testplan.html73
-rw-r--r--plans/testplan2html.py139
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()