Lib.robot.conf.settings.py Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sikulixapi Show documentation
Show all versions of sikulixapi Show documentation
... for visual testing and automation
# Copyright 2008-2015 Nokia Solutions and Networks
#
# 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 os
import random
import sys
import time
from robot.errors import DataError, FrameworkError
from robot.output import LOGGER, loggerhelper
from robot.result.keywordremover import KeywordRemover
from robot.result.flattenkeywordmatcher import validate_flatten_keyword
from robot.utils import (abspath, escape, format_time, get_link_path,
html_escape, is_list_like, py2to3,
split_args_from_name_or_path)
from .gatherfailed import gather_failed_tests
@py2to3
class _BaseSettings(object):
_cli_opts = {'Name' : ('name', None),
'Doc' : ('doc', None),
'Metadata' : ('metadata', []),
'TestNames' : ('test', []),
'ReRunFailed' : ('rerunfailed', 'NONE'),
'SuiteNames' : ('suite', []),
'SetTag' : ('settag', []),
'Include' : ('include', []),
'Exclude' : ('exclude', []),
'Critical' : ('critical', None),
'NonCritical' : ('noncritical', None),
'OutputDir' : ('outputdir', abspath('.')),
'Log' : ('log', 'log.html'),
'Report' : ('report', 'report.html'),
'XUnit' : ('xunit', None),
'SplitLog' : ('splitlog', False),
'TimestampOutputs' : ('timestampoutputs', False),
'LogTitle' : ('logtitle', None),
'ReportTitle' : ('reporttitle', None),
'ReportBackground' : ('reportbackground',
('#9e9', '#9e9', '#f66')),
'SuiteStatLevel' : ('suitestatlevel', -1),
'TagStatInclude' : ('tagstatinclude', []),
'TagStatExclude' : ('tagstatexclude', []),
'TagStatCombine' : ('tagstatcombine', []),
'TagDoc' : ('tagdoc', []),
'TagStatLink' : ('tagstatlink', []),
'RemoveKeywords' : ('removekeywords', []),
'FlattenKeywords' : ('flattenkeywords', []),
'PreRebotModifiers': ('prerebotmodifier', []),
'StatusRC' : ('statusrc', True),
'ConsoleColors' : ('consolecolors', 'AUTO'),
'StdOut' : ('stdout', None),
'StdErr' : ('stderr', None),
'XUnitSkipNonCritical' : ('xunitskipnoncritical', False)}
_output_opts = ['Output', 'Log', 'Report', 'XUnit', 'DebugFile']
def __init__(self, options=None, **extra_options):
self.start_timestamp = format_time(time.time(), '', '-', '')
self._opts = {}
self._cli_opts = self._cli_opts.copy()
self._cli_opts.update(self._extra_cli_opts)
self._process_cli_opts(dict(options or {}, **extra_options))
def _process_cli_opts(self, opts):
for name, (cli_name, default) in self._cli_opts.items():
value = opts[cli_name] if cli_name in opts else default
if default == [] and not is_list_like(value):
value = [value]
self[name] = self._process_value(name, value)
self['TestNames'] += self['ReRunFailed']
def __setitem__(self, name, value):
if name not in self._cli_opts:
raise KeyError("Non-existing option '%s'." % name)
self._opts[name] = value
def _process_value(self, name, value):
if name == 'ReRunFailed':
return gather_failed_tests(value)
if name == 'LogLevel':
return self._process_log_level(value)
if value == self._get_default_value(name):
return value
if name in ['Name', 'Doc', 'LogTitle', 'ReportTitle']:
if name == 'Doc':
value = self._escape_as_data(value)
return value.replace('_', ' ')
if name in ['Metadata', 'TagDoc']:
if name == 'Metadata':
value = [self._escape_as_data(v) for v in value]
return [self._process_metadata_or_tagdoc(v) for v in value]
if name in ['Include', 'Exclude']:
return [self._format_tag_patterns(v) for v in value]
if name in self._output_opts and (not value or value.upper() == 'NONE'):
return None
if name == 'OutputDir':
return abspath(value)
if name in ['SuiteStatLevel', 'ConsoleWidth']:
return self._convert_to_positive_integer_or_default(name, value)
if name == 'VariableFiles':
return [split_args_from_name_or_path(item) for item in value]
if name == 'ReportBackground':
return self._process_report_background(value)
if name == 'TagStatCombine':
return [self._process_tag_stat_combine(v) for v in value]
if name == 'TagStatLink':
return [v for v in [self._process_tag_stat_link(v) for v in value] if v]
if name == 'Randomize':
return self._process_randomize_value(value)
if name == 'RemoveKeywords':
self._validate_remove_keywords(value)
if name == 'FlattenKeywords':
self._validate_flatten_keywords(value)
return value
def _escape_as_data(self, value):
return value
def _process_log_level(self, level):
level, visible_level = self._split_log_level(level.upper())
self._opts['VisibleLogLevel'] = visible_level
return level
def _split_log_level(self, level):
if ':' in level:
level, visible_level = level.split(':', 1)
else:
visible_level = level
self._validate_log_level_and_default(level, visible_level)
return level, visible_level
def _validate_log_level_and_default(self, log_level, default):
if log_level not in loggerhelper.LEVELS:
raise DataError("Invalid log level '%s'" % log_level)
if default not in loggerhelper.LEVELS:
raise DataError("Invalid log level '%s'" % default)
if not loggerhelper.IsLogged(log_level)(default):
raise DataError("Default visible log level '%s' is lower than "
"log level '%s'" % (default, log_level))
def _process_randomize_value(self, original):
value = original.lower()
if ':' in value:
value, seed = value.split(':', 1)
else:
seed = random.randint(0, sys.maxsize)
if value in ('test', 'suite'):
value += 's'
if value not in ('tests', 'suites', 'none', 'all'):
self._raise_invalid_option_value('--randomize', original)
try:
seed = int(seed)
except ValueError:
self._raise_invalid_option_value('--randomize', original)
return value, seed
def _raise_invalid_option_value(self, option_name, given_value):
raise DataError("Option '%s' does not support value '%s'."
% (option_name, given_value))
def __getitem__(self, name):
if name not in self._opts:
raise KeyError("Non-existing option '%s'." % name)
if name in self._output_opts:
return self._get_output_file(name)
return self._opts[name]
def _get_output_file(self, option):
"""Returns path of the requested output file and creates needed dirs.
`option` can be 'Output', 'Log', 'Report', 'XUnit' or 'DebugFile'.
"""
name = self._opts[option]
if not name:
return None
if option == 'Log' and self._output_disabled():
self['Log'] = None
LOGGER.error('Log file is not created if output.xml is disabled.')
return None
name = self._process_output_name(option, name)
path = abspath(os.path.join(self['OutputDir'], name))
self._create_output_dir(os.path.dirname(path), option)
return path
def _process_output_name(self, option, name):
base, ext = os.path.splitext(name)
if self['TimestampOutputs']:
base = '%s-%s' % (base, self.start_timestamp)
ext = self._get_output_extension(ext, option)
return base + ext
def _get_output_extension(self, ext, type_):
if ext != '':
return ext
if type_ in ['Output', 'XUnit']:
return '.xml'
if type_ in ['Log', 'Report']:
return '.html'
if type_ == 'DebugFile':
return '.txt'
raise FrameworkError("Invalid output file type: %s" % type_)
def _create_output_dir(self, path, type_):
try:
if not os.path.exists(path):
os.makedirs(path)
except EnvironmentError as err:
raise DataError("Creating %s file directory '%s' failed: %s"
% (type_.lower(), path, err.strerror))
def _process_metadata_or_tagdoc(self, value):
value = value.replace('_', ' ')
if ':' in value:
return value.split(':', 1)
return value, ''
def _process_report_background(self, colors):
if colors.count(':') not in [1, 2]:
raise DataError("Invalid report background colors '%s'." % colors)
colors = colors.split(':')
if len(colors) == 2:
return colors[0], colors[0], colors[1]
return tuple(colors)
def _process_tag_stat_combine(self, pattern):
if ':' in pattern:
pattern, title = pattern.rsplit(':', 1)
else:
title = ''
return self._format_tag_patterns(pattern), title.replace('_', ' ')
def _format_tag_patterns(self, pattern):
for search, replace in [('&', 'AND'), ('AND', ' AND '), ('OR', ' OR '),
('NOT', ' NOT '), ('_', ' ')]:
if search in pattern:
pattern = pattern.replace(search, replace)
while ' ' in pattern:
pattern = pattern.replace(' ', ' ')
if pattern.startswith(' NOT'):
pattern = pattern[1:]
return pattern
def _process_tag_stat_link(self, value):
tokens = value.split(':')
if len(tokens) >= 3:
return tokens[0], ':'.join(tokens[1:-1]), tokens[-1]
raise DataError("Invalid format for option '--tagstatlink'. "
"Expected 'tag:link:title' but got '%s'." % value)
def _convert_to_positive_integer_or_default(self, name, value):
value = self._convert_to_integer(name, value)
return value if value > 0 else self._get_default_value(name)
def _convert_to_integer(self, name, value):
try:
return int(value)
except ValueError:
raise DataError("Option '--%s' expected integer value but got '%s'."
% (name.lower(), value))
def _get_default_value(self, name):
return self._cli_opts[name][1]
def _validate_remove_keywords(self, values):
for value in values:
try:
KeywordRemover(value)
except DataError as err:
raise DataError("Invalid value for option '--removekeywords'. %s" % err)
def _validate_flatten_keywords(self, values):
try:
validate_flatten_keyword(values)
except DataError as err:
raise DataError("Invalid value for option '--flattenkeywords'. %s" % err)
def __contains__(self, setting):
return setting in self._cli_opts
def __unicode__(self):
return '\n'.join('%s: %s' % (name, self._opts[name])
for name in sorted(self._opts))
@property
def output_directory(self):
return self['OutputDir']
@property
def output(self):
return self['Output']
@property
def log(self):
return self['Log']
@property
def report(self):
return self['Report']
@property
def xunit(self):
return self['XUnit']
@property
def log_level(self):
return self['LogLevel']
@property
def split_log(self):
return self['SplitLog']
@property
def status_rc(self):
return self['StatusRC']
@property
def xunit_skip_noncritical(self):
return self['XUnitSkipNonCritical']
@property
def statistics_config(self):
return {
'suite_stat_level': self['SuiteStatLevel'],
'tag_stat_include': self['TagStatInclude'],
'tag_stat_exclude': self['TagStatExclude'],
'tag_stat_combine': self['TagStatCombine'],
'tag_stat_link': self['TagStatLink'],
'tag_doc': self['TagDoc'],
}
@property
def critical_tags(self):
return self['Critical']
@property
def non_critical_tags(self):
return self['NonCritical']
@property
def remove_keywords(self):
return self['RemoveKeywords']
@property
def flatten_keywords(self):
return self['FlattenKeywords']
@property
def pre_rebot_modifiers(self):
return self['PreRebotModifiers']
@property
def console_colors(self):
return self['ConsoleColors']
class RobotSettings(_BaseSettings):
_extra_cli_opts = {'Output' : ('output', 'output.xml'),
'LogLevel' : ('loglevel', 'INFO'),
'DryRun' : ('dryrun', False),
'ExitOnFailure' : ('exitonfailure', False),
'ExitOnError' : ('exitonerror', False),
'SkipTeardownOnExit' : ('skipteardownonexit', False),
'Randomize' : ('randomize', 'NONE'),
'RunEmptySuite' : ('runemptysuite', False),
'WarnOnSkipped' : ('warnonskippedfiles', False),
'Variables' : ('variable', []),
'VariableFiles' : ('variablefile', []),
'PreRunModifiers' : ('prerunmodifier', []),
'Listeners' : ('listener', []),
'ConsoleType' : ('console', 'verbose'),
'ConsoleTypeDotted' : ('dotted', False),
'ConsoleTypeQuiet' : ('quiet', False),
'ConsoleWidth' : ('consolewidth', 78),
'ConsoleMarkers' : ('consolemarkers', 'AUTO'),
'DebugFile' : ('debugfile', None)}
def get_rebot_settings(self):
settings = RebotSettings()
settings._opts.update(self._opts)
for name in ['Variables', 'VariableFiles', 'Listeners']:
del(settings._opts[name])
for name in ['Include', 'Exclude', 'TestNames', 'SuiteNames', 'Metadata']:
settings._opts[name] = []
for name in ['Name', 'Doc']:
settings._opts[name] = None
settings._opts['Output'] = None
settings._opts['LogLevel'] = 'TRACE'
settings._opts['ProcessEmptySuite'] = self['RunEmptySuite']
return settings
def _output_disabled(self):
return self.output is None
def _escape_as_data(self, value):
return escape(value)
@property
def listeners(self):
return self['Listeners']
@property
def debug_file(self):
return self['DebugFile']
@property
def suite_config(self):
return {
'name': self['Name'],
'doc': self['Doc'],
'metadata': dict(self['Metadata']),
'set_tags': self['SetTag'],
'include_tags': self['Include'],
'exclude_tags': self['Exclude'],
'include_suites': self['SuiteNames'],
'include_tests': self['TestNames'],
'empty_suite_ok': self.run_empty_suite,
'randomize_suites': self.randomize_suites,
'randomize_tests': self.randomize_tests,
'randomize_seed': self.randomize_seed,
}
@property
def randomize_seed(self):
return self['Randomize'][1]
@property
def randomize_suites(self):
return self['Randomize'][0] in ('suites', 'all')
@property
def randomize_tests(self):
return self['Randomize'][0] in ('tests', 'all')
@property
def dry_run(self):
return self['DryRun']
@property
def exit_on_failure(self):
return self['ExitOnFailure']
@property
def exit_on_error(self):
return self['ExitOnError']
@property
def skip_teardown_on_exit(self):
return self['SkipTeardownOnExit']
@property
def console_output_config(self):
return {
'type': self.console_type,
'width': self.console_width,
'colors': self.console_colors,
'markers': self.console_markers,
'stdout': self['StdOut'],
'stderr': self['StdErr']
}
@property
def console_type(self):
if self['ConsoleTypeQuiet']:
return 'quiet'
if self['ConsoleTypeDotted']:
return 'dotted'
return self['ConsoleType']
@property
def console_width(self):
return self['ConsoleWidth']
@property
def console_markers(self):
return self['ConsoleMarkers']
@property
def pre_run_modifiers(self):
return self['PreRunModifiers']
@property
def run_empty_suite(self):
return self['RunEmptySuite']
@property
def variables(self):
return self['Variables']
@property
def variable_files(self):
return self['VariableFiles']
class RebotSettings(_BaseSettings):
_extra_cli_opts = {'Output' : ('output', None),
'LogLevel' : ('loglevel', 'TRACE'),
'ProcessEmptySuite' : ('processemptysuite', False),
'StartTime' : ('starttime', None),
'EndTime' : ('endtime', None),
'Merge' : ('merge', False)}
def _output_disabled(self):
return False
@property
def suite_config(self):
return {
'name': self['Name'],
'doc': self['Doc'],
'metadata': dict(self['Metadata']),
'set_tags': self['SetTag'],
'include_tags': self['Include'],
'exclude_tags': self['Exclude'],
'include_suites': self['SuiteNames'],
'include_tests': self['TestNames'],
'empty_suite_ok': self.process_empty_suite,
'remove_keywords': self.remove_keywords,
'log_level': self['LogLevel'],
'critical_tags': self.critical_tags,
'non_critical_tags': self.non_critical_tags,
'start_time': self['StartTime'],
'end_time': self['EndTime']
}
@property
def log_config(self):
if not self.log:
return {}
return {
'title': html_escape(self['LogTitle'] or ''),
'reportURL': self._url_from_path(self.log, self.report),
'splitLogBase': os.path.basename(os.path.splitext(self.log)[0]),
'defaultLevel': self['VisibleLogLevel']
}
@property
def report_config(self):
if not self.report:
return {}
return {
'title': html_escape(self['ReportTitle'] or ''),
'logURL': self._url_from_path(self.report, self.log),
'background' : self._resolve_background_colors(),
}
def _url_from_path(self, source, destination):
if not destination:
return None
return get_link_path(destination, os.path.dirname(source))
def _resolve_background_colors(self):
colors = self['ReportBackground']
return {'pass': colors[0], 'nonCriticalFail': colors[1], 'fail': colors[2]}
@property
def merge(self):
return self['Merge']
@property
def console_output_config(self):
return {
'colors': self.console_colors,
'stdout': self['StdOut'],
'stderr': self['StdErr']
}
@property
def process_empty_suite(self):
return self['ProcessEmptySuite']
© 2015 - 2025 Weber Informatics LLC | Privacy Policy