Skip to content
Snippets Groups Projects
renderer.py 9.62 KiB
Newer Older
###################################################################################
# Test case renderer.
# Create a HTML page from a YAML test case file.
#
# Copyright (C) 2018
# Luis Araujo <luis.araujo@collabora.co.uk>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.

# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.

# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  US
###################################################################################

import os
import yaml
from jinja2 import Environment, PackageLoader
from atc_renderer.parser import parse_format
from atc_renderer.exceptions import ParserTypeError, ParserMissingFieldError
PYTHON_PKGNAME='atc_renderer'

# This command parses each line of a list and returns a structure of the form
# (comment, command, output) to apply the following formatting for the respective
# sections:
#
# pre-conditions:
# notes:
# expected:
#  - Start line with '$' for commands.
#  - Start line with '>' for command output.
#  - Start line with '@' to attach image.
#  - Start line with '~' to add web link.
#  - Everything else is a comment.
#
# run: steps: (only for automated tests)
#  - Start line with '#' for comments , everything else is a command.
#
def parse_list(lines, automated_run=False):
    processed_lines = []
    for line in lines:        
        p, c = '', ''
        sline = line.strip()
        if not sline:
            continue

        if automated_run:
            if sline[0] == '#':
                # Remove the '#' with the slice [1:]
                processed_lines.append((sline[1:], '', '', '', ''))
            else:
                # Add the '$ ' in the line for the command
                processed_lines.append(('', '$ '+sline, '', '', ''))
        else:
            if sline[0] == '$':
                processed_lines.append(('', sline, '', '', ''))
            elif sline[0] == '>':
                processed_lines.append(('', '', sline[1:].split('\n'), '', ''))
            elif sline[0] == '@':
                processed_lines.append(('', '', '', sline[1:], ''))
            elif sline[0] == '~':
                processed_lines.append(('', '', '', '', sline[1:]))
            else:
                processed_lines.append((sline, '', '', '', ''))

    return processed_lines

def priority_color(priority):
    return \
        (priority == 'low'      and 'secondary') or \
        (priority == 'medium'   and 'info')      or \
        (priority == 'high'     and 'warning')   or \
        (priority == 'critical' and 'danger')    or 'light'

def get_git_repo_dir(git_repo=None):
    if git_repo:
        try:
            return os.path.splitext(os.path.split(git_repo)[1])[0]
        except IndexError:
            print("Incorrect git-repos repository path:", git_repo)
            exit(1)

def get_template_values(testcase_data):
    template_values = {}
    is_automated = False

    # Mandatory fields
    metadata = testcase_data.get('metadata')
    if not metadata:
        print("Error: missing mandatory field metadata")
        exit(1)

    for mv in ['name', 'image-type', 'image-arch', 'type',
               'exec-type', 'priority', 'description', 'expected']:
        value = metadata.get(mv)
        if value:
            if mv == 'exec-type' and (value in ['all', 'automated']):
                is_automated = True
            if mv == 'expected':
                template_values.update({ mv : parse_list(value) })
            else:
                # Set the priority_color.
                if mv == 'priority':
                    template_values.update({ 'priority_color' :
                                             priority_color(value) })
                template_values.update({ mv.replace('-', '_') : value })
        else:
            print("Error: missing mandatory field", mv)
            exit(1)

    run = testcase_data.get('run')
    if not run:
        print("Error: missing mandatory field run")
        exit(1)
    else:
        steps = run.get('steps')
        if not steps:
            print("Error: missing mandatory field steps for run")
            exit(1)
        # 'is_automated' so automated tests steps are parsed differently.
        template_values.update({ 'run_steps' :
                                 parse_list(steps, automated_run=is_automated) })

    # If the test is automated, it should always have 'install:git-repos'
    install = testcase_data.get('install')
    if is_automated and install:
        git_repos = install.get('git-repos', [])
        git_repo_url, git_repo_branch = None, None
        for gr in git_repos:
            git_repo_url, git_repo_branch = gr.get('url'), gr.get('branch')
            if not git_repo_url or not git_repo_branch:
                print("Error: missing mandatory field git-repos:url/branch for " \
                      "automated tests")
                exit(1)

        template_values.update({ 'git_repo_url' : git_repo_url })
        template_values.update({ 'git_repo_dir' : get_git_repo_dir(git_repo_url) })
        template_values.update({ 'git_repo_branch' : git_repo_branch })

        deps = install.get('deps')
        if deps:
            # A install.deps directive will always add a preconditions section
            # to install the required packages using the macro to install packages.
            template_values.update({ 'packages_list' : ' '.join(deps) })

    # No mandatory fields
    for nm in ['notes', 'format', 'maintainer', 'resources',
               'pre-conditions', 'post-conditions']:
        value = metadata.get(nm)
        if value:
            template_values.update({ nm.replace('-', '_') : parse_list(value)
                                     if nm in ['notes', 'pre-conditions',
                                               'post-conditions']
                                     else value })

    # Macros variables
    ostree_preconditions = metadata.get('macro_ostree_preconditions')
    if ostree_preconditions:
        template_values.update({ 'pkgname' : ostree_preconditions })

    packages_list = metadata.get('macro_install_packages_preconditions')
    if packages_list:
        template_values.update({ 'packages_list' :  packages_list })

    modules_preconditions = metadata.get('macro_modules_preconditions')
    if modules_preconditions:
        template_values.update({ 'libname' : modules_preconditions })

    return template_values

def generate_test_case_page(tc_file, directory, return_out=False):
    try:
        with open(tc_file) as testcase:
            tc_data = yaml.safe_load(testcase)
    except yaml.scanner.ScannerError as e:
        print("yaml format error:", e)
        exit(1)

    # Parse file to detect any syntax error.
Luis Araujo's avatar
Luis Araujo committed
    if not return_out:
        # Just print info line if output is not returned from the method.
        print("Parsing file", tc_file)
    try:
        parse_format(tc_data)
    except (ParserTypeError, ParserMissingFieldError) as error:
        print("Error: " + str(error))
        exit(1)

    env = Environment(loader=PackageLoader(PYTHON_PKGNAME, 'templates/'))
    # Get template from environment and render it.
    data = env.get_template('test_case.html').render(get_template_values(tc_data))
Luis Araujo's avatar
Luis Araujo committed
    # Return the data if return_out=True
    if return_out:
        return data

    filename = os.path.splitext(os.path.basename(tc_file))[0] + ".html"
    print("Generating test case page", filename)
    with open(os.path.join(directory, filename), 'w') as test_case_html_file:
        test_case_html_file.write(data)

def generate_index_page(tc_files, directory):
    # Sort alphabetically in the index.
    tc_files.sort()
    # The `index` list is the main structure, each element has the form:
    # (name, description, exec-type, priority, priority_color, filename)
    index = []
    # `counts` is used to collect stats.
    counts = { 'automated' : 0, 'manual' : 0, 'all' : 0,
               'low' : 0, 'medium' : 0, 'high' : 0, 'critical' : 0,
               'total' : 0 }

    print("Generating index page ...")
    for f in tc_files:
        filename = os.path.splitext(os.path.basename(f))[0]
        try:
            with open(f) as testcase:
                tc_data = yaml.safe_load(testcase)
        except yaml.scanner.ScannerError as e:
            print("yaml format error:", e)
            exit(1)

        priority = tc_data['metadata']['priority']
        exec_type = tc_data['metadata']['exec-type']
        index.append((tc_data['metadata']['name'],
                      tc_data['metadata']['description'],
                      exec_type, priority, priority_color(priority), filename))

        # Collect some stats to show in the page.
        counts[exec_type] = counts.copy()[exec_type] + 1
        counts[priority] = counts.copy()[priority] + 1
        counts['total'] = counts.copy()['total'] + 1

    env = Environment(loader=PackageLoader(PYTHON_PKGNAME, 'templates/'))
    # Get template from environment and render it.
    template_values = { 'index_files': index, 'counts': counts }
    data = env.get_template('index.html').render(template_values)

    index_path = os.path.join(directory, 'index.html')
    with open(index_path, 'w') as index_html_file:
        index_html_file.write(data)
    print("Index page created at", index_path)