1# Copyright 2014 LinkedIn Corp. 2# 3# Licensed to the Apache Software Foundation (ASF) under one 4# or more contributor license agreements. See the NOTICE file 5# distributed with this work for additional information 6# regarding copyright ownership. The ASF licenses this file 7# to you under the Apache License, Version 2.0 (the 8# "License"); you may not use this file except in compliance 9# with the License. You may obtain a copy of the License at 10# 11# http://www.apache.org/licenses/LICENSE-2.0 12# 13# Unless required by applicable law or agreed to in writing, 14# software distributed under the License is distributed on an 15# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16# KIND, either express or implied. See the License for the 17# specific language governing permissions and limitations 18# under the License. 19 20""" 21Class used to generate the report. 22""" 23import os 24from jinja2 import Environment, FileSystemLoader 25 26import zopkio.constants as constants 27import zopkio.runtime as runtime 28import zopkio.utils as utils 29 30 31class _ReportInfo(object): 32 """ 33 Holds data shared among all report pages 34 """ 35 def __init__(self, output_dir, logs_dir, naarad_dir): 36 self.output_dir = os.path.abspath(output_dir) 37 self.resource_dir = os.path.join(output_dir, "resources/") 38 self.logs_dir = os.path.abspath(logs_dir) 39 self.naarad_dir = os.path.abspath(naarad_dir) 40 41 self.config_to_test_names_map = {} 42 43 self.report_file_sfx = "_report.html" 44 45 self.home_page = os.path.join(output_dir, "report.html") 46 self.diff_page = os.path.join(output_dir, "diff.html") 47 self.log_page = os.path.join(output_dir, "log.html") 48 self.project_url = "https://github.com/linkedin/Zopkio" 49 50 self.results_map = { 51 "passed": constants.PASSED, 52 "failed": constants.FAILED, 53 "skipped": constants.SKIPPED 54 } 55 56 57class Reporter(object): 58 """ 59 Class that converts the aggregated output into a user-friendly web page. 60 """ 61 def __init__(self, report_name, output_dir, logs_dir, naarad_dir): 62 """ 63 :param report_name: used in the title of the front-end 64 :param output_dir: directory where the report will be generated 65 :param logs_dir: directory of where the logs will be collected 66 :param naarad_dir: directory containing the naarad reports 67 """ 68 self.name = report_name 69 self.env = Environment(loader=FileSystemLoader(constants.WEB_RESOURCE_DIR)) # used to load html pages for Jinja2 70 self.data_source = runtime.get_collector() 71 self.report_info = _ReportInfo(output_dir, logs_dir, naarad_dir) 72 73 def get_config_to_test_names_map(self): 74 config_to_test_names_map = {} 75 for config_name in self.data_source.get_config_names(): 76 config_to_test_names_map[config_name] = self.data_source.get_test_names(config_name) 77 return config_to_test_names_map 78 79 def get_report_location(self): 80 """ 81 Returns the filename of the landing page 82 """ 83 return self.report_info.home_page 84 85 def generate(self): 86 """ 87 Generates the report 88 """ 89 self._setup() 90 91 header_html = self._generate_header() 92 footer_html = self._generate_footer() 93 results_topbar_html = self._generate_topbar("results") 94 summary_topbar_html = self._generate_topbar("summary") 95 logs_topbar_html = self._generate_topbar("logs") 96 diff_topbar_html = self._generate_topbar("diff") 97 98 summary_body_html = self._generate_summary_body() 99 diff_body_html = self._generate_diff_body() 100 summary_html = header_html + summary_topbar_html + summary_body_html + footer_html 101 diff_html = header_html + diff_topbar_html + diff_body_html+ footer_html 102 Reporter._make_file(summary_html, self.report_info.home_page) 103 Reporter._make_file(diff_html,self.report_info.diff_page) 104 105 log_body_html = self._generate_log_body() 106 log_html = header_html + logs_topbar_html + log_body_html+footer_html 107 Reporter._make_file(log_html, self.report_info.log_page) 108 109 for config_name in self.report_info.config_to_test_names_map.keys(): 110 config_dir = os.path.join(self.report_info.resource_dir, config_name) 111 utils.makedirs(config_dir) 112 113 config_body_html = self._generate_config_body(config_name) 114 config_html = header_html + results_topbar_html + config_body_html + footer_html 115 config_file = os.path.join(config_dir, config_name + self.report_info.report_file_sfx) 116 Reporter._make_file(config_html, config_file) 117 118 for test_name in self.data_source.get_test_names(config_name): 119 test_body_html = self._generate_test_body(config_name, test_name) 120 test_html = header_html + results_topbar_html + test_body_html + footer_html 121 test_file = os.path.join(config_dir, test_name + self.report_info.report_file_sfx) 122 Reporter._make_file(test_html, test_file) 123 124 def _generate_config_body(self, config_name): 125 summary_stats = [ 126 self.data_source.count_tests(config_name), 127 self.data_source.count_tests_with_result(config_name, constants.PASSED), 128 self.data_source.count_tests_with_result(config_name, constants.FAILED), 129 self.data_source.count_tests_with_result(config_name, constants.SKIPPED), 130 self.data_source.get_config_exec_time(config_name), 131 self.data_source.get_config_start_time(config_name), 132 self.data_source.get_config_end_time(config_name) 133 ] 134 135 config_template = self.env.get_template("config_page.html") 136 config_body_html = config_template.render( 137 config_data=self.data_source.get_config_result(config_name), 138 tests=self.data_source.get_test_results(config_name), 139 report_info=self.report_info, 140 summary=summary_stats 141 ) 142 143 return config_body_html 144 145 146 def _generate_log_body(self): 147 log_template = self.env.get_template("logs_page.html") 148 log_body_html = log_template.render(logs_dir=self.report_info.logs_dir) 149 return log_body_html 150 151 def _generate_footer(self): 152 footer_template = self.env.get_template("footer.html") 153 footer_html = footer_template.render() 154 return footer_html 155 156 def _generate_header(self): 157 CSS_INCLUDES = [ 158 "web_resources/style.css" 159 ] 160 CSS_INCLUDES[:] = [os.path.join(constants.PROJECT_ROOT_DIR, css_include) for css_include in CSS_INCLUDES] 161 162 JS_INCLUDES = [ 163 "web_resources/script.js" 164 ] 165 JS_INCLUDES[:] = [os.path.join(constants.PROJECT_ROOT_DIR, js_include) for js_include in JS_INCLUDES] 166 header_template = self.env.get_template("header.html") 167 header_html = header_template.render( 168 page_title=self.name, 169 css_includes=CSS_INCLUDES, 170 js_includes=JS_INCLUDES 171 ) 172 return header_html 173 174 def _generate_diff_body(self): 175 176 diff_body_html = "" 177 config_tests_dict = {} 178 config_data_dict = {} 179 180 #generate diff page only if multiple configs exist 181 if (len(self.report_info.config_to_test_names_map.keys()) > 1): 182 # get list of test names in sorted order 183 test_names = self.data_source.get_test_results(self.report_info.config_to_test_names_map.keys()[0]) 184 test_names.sort(key=lambda x: x.name) 185 186 for config_name in self.report_info.config_to_test_names_map.keys(): 187 config_tests = self.data_source.get_test_results(config_name) 188 config_tests.sort(key=lambda x: x.name) 189 config_tests_dict[config_name] = config_tests 190 config_data_dict[config_name] = self.data_source.get_config_result(config_name) 191 192 diff_template = self.env.get_template("diff.html") 193 diff_body_html = diff_template.render( 194 test_names = test_names, 195 report_info = self.report_info, 196 config_names = self.report_info.config_to_test_names_map.keys(), 197 config_tests_dict = config_tests_dict, 198 config_data_dict = config_data_dict 199 ) 200 return diff_body_html 201 202 def _generate_summary_body(self): 203 summary_stats = [ 204 self.data_source.count_all_tests(), 205 self.data_source.count_all_tests_with_result(constants.PASSED), 206 self.data_source.count_all_tests_with_result(constants.FAILED), 207 self.data_source.count_all_tests_with_result(constants.SKIPPED), 208 self.data_source.get_total_config_exec_time(), 209 self.data_source.get_summary_start_time(), 210 self.data_source.get_summary_end_time() 211 ] 212 213 config_failure_map = {} 214 config_total_tests_map = {} 215 config_test_failure_map = {} 216 config_test_skipped_map = {} 217 config_test_passed_map = {} 218 for config_name in self.report_info.config_to_test_names_map.keys(): 219 config_total_tests_map[config_name] = self.data_source.count_tests(config_name) 220 config_failure_map[config_name] = self.data_source.get_config_result(config_name).result 221 config_test_failure_map[config_name] = self.data_source.count_tests_with_result(config_name, constants.FAILED) 222 config_test_skipped_map[config_name] = self.data_source.count_tests_with_result(config_name, constants.SKIPPED) 223 config_test_passed_map[config_name] = self.data_source.count_tests_with_result(config_name, constants.PASSED) 224 225 summary_template = self.env.get_template("landing_page.html") 226 summary_body = summary_template.render( 227 report_info=self.report_info, 228 summary=summary_stats, 229 config_fail=config_failure_map, 230 config_fail_map=config_test_failure_map, 231 config_skip_map=config_test_skipped_map, 232 config_tests_map = config_total_tests_map, 233 config_pass_map = config_test_passed_map 234 ) 235 return summary_body 236 237 def _generate_topbar(self, active_page): 238 topbar_template = self.env.get_template("topbar.html") 239 topbar_html = topbar_template.render( 240 report_info=self.report_info, 241 active=active_page, 242 ) 243 return topbar_html 244 245 def _generate_test_body(self, config_name, test_name): 246 test_template = self.env.get_template("test_page.html") 247 test_body = test_template.render( 248 config_name=config_name, 249 test_data=self.data_source.get_test_result(config_name, test_name), 250 report_info=self.report_info, 251 config_data=self.data_source.get_config_result(config_name) 252 ) 253 return test_body 254 255 @staticmethod 256 def _make_file(html, location): 257 with open(location, "w") as f: 258 f.write(html) 259 260 def _setup(self): 261 utils.makedirs(self.report_info.output_dir) 262 utils.makedirs(self.report_info.resource_dir) 263 self.report_info.config_to_test_names_map = self.get_config_to_test_names_map() 264