################################################################################### # 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. 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)) # 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)