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