Source code for robot.reporting.jsmodelbuilders

#  Copyright 2008-2015 Nokia Networks
#  Copyright 2016-     Robot Framework Foundation
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

import re

from robot.output import LEVELS
from robot.result import Error, Keyword, Message, Return

from .jsbuildingcontext import JsBuildingContext
from .jsexecutionresult import JsExecutionResult

STATUSES = {"FAIL": 0, "PASS": 1, "SKIP": 2, "NOT RUN": 3}
KEYWORD_TYPES = {
    "KEYWORD": 0,
    "SETUP": 1,
    "TEARDOWN": 2,
    "FOR": 3,
    "ITERATION": 4,
    "IF": 5,
    "ELSE IF": 6,
    "ELSE": 7,
    "RETURN": 8,
    "VAR": 9,
    "TRY": 10,
    "EXCEPT": 11,
    "FINALLY": 12,
    "WHILE": 13,
    "GROUP": 14,
    "CONTINUE": 15,
    "BREAK": 16,
    "ERROR": 17,
}


[docs] class JsModelBuilder: def __init__( self, log_path=None, split_log=False, expand_keywords=None, prune_input_to_save_memory=False, ): self._context = JsBuildingContext( log_path, split_log, expand_keywords, prune_input_to_save_memory, )
[docs] def build_from(self, result_from_xml): # Statistics must be build first because building suite may prune input. return JsExecutionResult( statistics=StatisticsBuilder().build(result_from_xml.statistics), suite=SuiteBuilder(self._context).build(result_from_xml.suite), errors=ErrorsBuilder(self._context).build(result_from_xml.errors), strings=self._context.strings, basemillis=self._context.basemillis, split_results=self._context.split_results, min_level=self._context.min_level, expand_keywords=self._context.expand_keywords, )
[docs] class Builder: robot_note = re.compile('<span class="robot-note">(.*)</span>') def __init__(self, context: JsBuildingContext): self._context = context self._string = self._context.string self._html = self._context.html self._timestamp = self._context.timestamp def _get_status(self, item, note_only=False): model = ( STATUSES[item.status], self._timestamp(item.start_time), round(item.elapsed_time.total_seconds() * 1000), ) msg = item.message if not msg: return model if note_only: if msg.startswith("*HTML*"): match = self.robot_note.search(msg) if match: index = self._string(match.group(1)) return (*model, index) return model if msg.startswith("*HTML*"): index = self._string(msg[6:].lstrip(), escape=False) else: index = self._string(msg) return (*model, index) def _build_body(self, body, split=False): splitting = self._context.start_splitting_if_needed(split) # tuple([<listcomp>]) is faster than tuple(<genex>) with short lists. model = tuple([self._build_body_item(item) for item in body]) return model if not splitting else self._context.end_splitting(model) def _build_body_item(self, item): raise NotImplementedError
[docs] class SuiteBuilder(Builder): def __init__(self, context): super().__init__(context) self._build_suite = self.build self._build_test = TestBuilder(context).build self._build_body_item = BodyItemBuilder(context).build
[docs] def build(self, suite): with self._context.prune_input(suite.tests, suite.suites): stats = self._get_statistics(suite) # Must be done before pruning fixture = [] if suite.has_setup: fixture.append(suite.setup) if suite.has_teardown: fixture.append(suite.teardown) return ( self._string(suite.name, attr=True), self._string(suite.source), self._context.relative_source(suite.source), self._html(suite.doc), tuple(self._yield_metadata(suite)), self._get_status(suite), tuple(self._build_suite(s) for s in suite.suites), tuple(self._build_test(t) for t in suite.tests), tuple(self._build_body_item(kw, split=True) for kw in fixture), stats, )
def _yield_metadata(self, suite): for name, value in suite.metadata.items(): yield self._string(name) yield self._html(value) def _get_statistics(self, suite): stats = suite.statistics # Access property only once return (stats.total, stats.passed, stats.failed, stats.skipped)
[docs] class TestBuilder(Builder): def __init__(self, context): super().__init__(context) self._build_body_item = BodyItemBuilder(context).build
[docs] def build(self, test): body = self._get_body_items(test) with self._context.prune_input(test.body): return ( self._string(test.name, attr=True), self._string(test.timeout), self._html(test.doc), tuple(self._string(t) for t in test.tags), self._get_status(test), self._build_body(body, split=True), )
def _get_body_items(self, test): body = test.body.flatten() if test.has_setup: body.insert(0, test.setup) if test.has_teardown: body.append(test.teardown) return body
[docs] class BodyItemBuilder(Builder): def __init__(self, context): super().__init__(context) self._build_body_item = self.build self._build_message = MessageBuilder(context).build
[docs] def build(self, item, split=False): if isinstance(item, Message): return self._build_message(item) with self._context.prune_input(item.body): if isinstance(item, Keyword): return self._build_keyword(item, split) if isinstance(item, (Return, Error)): return self._build(item, args=" ".join(item.values), split=split) return self._build(item, item._log_name, split=split)
def _build_keyword(self, kw: Keyword, split): self._context.check_expansion(kw) body = kw.body.flatten() if kw.has_setup: body.insert(0, kw.setup) if kw.has_teardown: body.append(kw.teardown) return self._build( kw, kw.name, kw.owner, kw.timeout, kw.doc, " ".join(kw.args), " ".join(kw.assign), ", ".join(kw.tags), body, split=split, ) def _build( self, item, name="", owner="", timeout="", doc="", args="", assign="", tags="", body=None, split=False, ): if body is None: body = item.body.flatten() return ( KEYWORD_TYPES[item.type], self._string(name, attr=True), self._string(owner, attr=True), self._string(timeout), self._html(doc), self._string(args), self._string(assign), self._string(tags), self._get_status(item, note_only=True), self._build_body(body, split), )
[docs] class MessageBuilder(Builder):
[docs] def build(self, msg): if msg.level in ("WARN", "ERROR"): self._context.create_link_target(msg) self._context.message_level(msg.level) return self._build(msg)
def _build(self, msg): return ( self._timestamp(msg.timestamp), LEVELS[msg.level], self._string(msg.html_message, escape=False), )
[docs] class StatisticsBuilder:
[docs] def build(self, statistics): return ( self._build_stats(statistics.total), self._build_stats(statistics.tags), self._build_stats(statistics.suite, exclude_empty=False), )
def _build_stats(self, stats, exclude_empty=True): return tuple( stat.get_attributes( include_label=True, include_elapsed=True, exclude_empty=exclude_empty, html_escape=True, ) for stat in stats )
[docs] class ErrorsBuilder(Builder): def __init__(self, context): super().__init__(context) self._build_message = ErrorMessageBuilder(context).build
[docs] def build(self, errors): with self._context.prune_input(errors.messages): return tuple(self._build_message(msg) for msg in errors)
[docs] class ErrorMessageBuilder(MessageBuilder):
[docs] def build(self, msg): model = self._build(msg) link = self._context.link(msg) return model if link is None else (*model, link)