# 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 os
import re
import tempfile
from robot.errors import DataError
from robot.model import Tags
from robot.output import LOGGER
from robot.utils import abspath, DotDict, find_file, get_error_details, NormalizedDict
from .resolvable import GlobalVariableValue
from .variables import Variables
[docs]
class VariableScopes:
def __init__(self, settings):
self._global = GlobalVariables(settings)
self._suite = None
self._test = None
self._suite_locals = []
self._scopes = [self._global]
self._variables_set = SetVariables()
@property
def current(self):
return self._scopes[-1]
@property
def _all_scopes(self):
return reversed(self._scopes)
@property
def _scopes_until_suite(self):
for scope in self._all_scopes:
yield scope
if scope is self._suite:
break
@property
def _scopes_until_test(self):
for scope in self._scopes_until_suite:
yield scope
if scope is self._test:
break
[docs]
def start_suite(self):
self._suite = self._global.copy()
self._scopes.append(self._suite)
self._suite_locals.append(NormalizedDict(ignore="_"))
self._variables_set.start_suite()
self._variables_set.update(self._suite)
[docs]
def end_suite(self):
self._scopes.pop()
self._suite_locals.pop()
self._suite = self._scopes[-1] if len(self._scopes) > 1 else None
self._variables_set.end_suite()
[docs]
def start_test(self):
self._test = self._suite.copy(update=self._suite_locals[-1])
self._scopes.append(self._test)
self._variables_set.start_test()
[docs]
def end_test(self):
self._scopes.pop()
self._test = None
self._variables_set.end_test()
[docs]
def start_keyword(self):
update = self._suite_locals[-1] if self._test else None
kw = self._suite.copy(update)
self._variables_set.start_keyword()
self._variables_set.update(kw)
self._scopes.append(kw)
[docs]
def end_keyword(self):
self._scopes.pop()
self._variables_set.end_keyword()
def __getitem__(self, name):
return self.current[name]
def __setitem__(self, name, value):
self.current[name] = value
def __contains__(self, name):
return name in self.current
[docs]
def replace_list(self, items, replace_until=None, ignore_errors=False):
return self.current.replace_list(items, replace_until, ignore_errors)
[docs]
def replace_scalar(self, items, ignore_errors=False):
return self.current.replace_scalar(items, ignore_errors)
[docs]
def replace_string(self, string, custom_unescaper=None, ignore_errors=False):
return self.current.replace_string(string, custom_unescaper, ignore_errors)
[docs]
def set_from_file(self, path, args, overwrite=False):
variables = None
for scope in self._scopes_until_suite:
if variables is None:
variables = scope.set_from_file(path, args, overwrite)
else:
scope.set_from_file(variables, overwrite=overwrite)
[docs]
def set_from_variable_section(self, variables, overwrite=False):
for scope in self._scopes_until_suite:
scope.set_from_variable_section(variables, overwrite)
[docs]
def resolve_delayed(self):
for scope in self._scopes_until_suite:
scope.resolve_delayed()
[docs]
def set_global(self, name, value):
for scope in self._all_scopes:
name, value = self._set_global_suite_or_test(scope, name, value)
self._variables_set.set_global(name, value)
def _set_global_suite_or_test(self, scope, name, value):
scope[name] = value
# Avoid creating new list/dict objects in different scopes.
if name[0] != "$":
name = "$" + name[1:]
value = scope[name]
return name, value
[docs]
def set_suite(self, name, value, top=False, children=False):
if top:
self._scopes[1][name] = value
return
for scope in self._scopes_until_suite:
name, value = self._set_global_suite_or_test(scope, name, value)
self._variables_set.set_suite(name, value, children)
# Override possible "suite local variables" (i.e. test variables set on
# suite level) if real suite level variable is set.
if name in self._suite_locals[-1]:
self._suite_locals[-1].pop(name)
[docs]
def set_test(self, name, value):
if self._test:
for scope in self._scopes_until_test:
name, value = self._set_global_suite_or_test(scope, name, value)
self._variables_set.set_test(name, value)
else:
# Set test scope variable on suite level. Keep track on added and
# overridden variables to allow updating variables when test starts.
prev = self._suite.get(name)
self.set_suite(name, value)
self._suite_locals[-1][name] = prev
[docs]
def set_keyword(self, name, value):
self.current[name] = value
self._variables_set.set_keyword(name, value)
[docs]
def set_local(self, name, value):
self.current[name] = value
[docs]
def as_dict(self, decoration=True):
return self.current.as_dict(decoration=decoration)
[docs]
class GlobalVariables(Variables):
_import_by_path_ends = (".py", "/", os.sep, ".yaml", ".yml", ".json")
def __init__(self, settings):
super().__init__()
self._set_built_in_variables(settings)
self._set_cli_variables(settings)
def _set_cli_variables(self, settings):
for name, args in settings.variable_files:
try:
if name.lower().endswith(self._import_by_path_ends):
name = find_file(name, file_type="Variable file")
self.set_from_file(name, args)
except Exception:
msg, details = get_error_details()
LOGGER.error(msg)
LOGGER.info(details)
for varstr in settings.variables:
match = re.fullmatch("([^:]+): ([^:]+):(.*)", varstr)
if match:
name, typ, value = match.groups()
value = self._convert_cli_variable(name, typ, value)
elif ":" in varstr:
name, value = varstr.split(":", 1)
else:
name, value = varstr, ""
self[f"${{{name}}}"] = value
def _convert_cli_variable(self, name, typ, value):
from robot.api.types import Secret
from robot.running import TypeInfo
var = f"${{{name}: {typ}}}"
try:
info = TypeInfo.from_variable(var)
except DataError as err:
raise DataError(f"Invalid command line variable '{var}': {err}")
if info.type is Secret:
return Secret(value)
try:
return info.convert(value, var, kind="Command line variable")
except ValueError as err:
raise DataError(str(err))
def _set_built_in_variables(self, settings):
options = DotDict(
rpa=settings.rpa,
include=Tags(settings.include),
exclude=Tags(settings.exclude),
skip=Tags(settings.skip),
skip_on_failure=Tags(settings.skip_on_failure),
console_width=settings.console_width,
)
for name, value in [
("${TEMPDIR}", abspath(tempfile.gettempdir())),
("${EXECDIR}", abspath(".")),
("${OPTIONS}", options),
("${/}", os.sep),
("${:}", os.pathsep),
("${\\n}", os.linesep),
("${SPACE}", " "),
("${True}", True),
("${False}", False),
("${None}", None),
("${null}", None),
("${OUTPUT_DIR}", str(settings.output_directory)),
("${OUTPUT_FILE}", str(settings.output or "NONE")),
("${REPORT_FILE}", str(settings.report or "NONE")),
("${LOG_FILE}", str(settings.log or "NONE")),
("${DEBUG_FILE}", str(settings.debug_file or "NONE")),
("${LOG_LEVEL}", settings.log_level),
("${PREV_TEST_NAME}", ""),
("${PREV_TEST_STATUS}", ""),
("${PREV_TEST_MESSAGE}", ""),
]:
self[name] = GlobalVariableValue(value)
[docs]
class SetVariables:
def __init__(self):
self._suite = None
self._test = None
self._scopes = []
[docs]
def start_suite(self):
if not self._scopes:
self._suite = NormalizedDict(ignore="_")
else:
self._suite = self._scopes[-1].copy()
self._scopes.append(self._suite)
[docs]
def end_suite(self):
self._scopes.pop()
self._suite = self._scopes[-1] if self._scopes else None
[docs]
def start_test(self):
self._test = self._scopes[-1].copy()
self._scopes.append(self._test)
[docs]
def end_test(self):
self._test = None
self._scopes.pop()
[docs]
def start_keyword(self):
self._scopes.append(self._scopes[-1].copy())
[docs]
def end_keyword(self):
self._scopes.pop()
[docs]
def set_global(self, name, value):
for scope in self._scopes:
if name in scope:
scope.pop(name)
[docs]
def set_suite(self, name, value, children=False):
for scope in reversed(self._scopes):
if children:
scope[name] = value
elif name in scope:
scope.pop(name)
if scope is self._suite:
break
[docs]
def set_test(self, name, value):
for scope in reversed(self._scopes):
scope[name] = value
if scope is self._test:
break
[docs]
def set_keyword(self, name, value):
self._scopes[-1][name] = value
[docs]
def update(self, variables):
for name, value in self._scopes[-1].items():
variables[name] = value