Skip to content

Commit

Permalink
Make TestSuite.source a pathlib.Path instance
Browse files Browse the repository at this point in the history
Also use pathlib.Path elsewhere. Fixes #4596.
  • Loading branch information
pekkaklarck committed Jan 13, 2023
1 parent ddfb05b commit 92148c8
Show file tree
Hide file tree
Showing 18 changed files with 94 additions and 82 deletions.
10 changes: 6 additions & 4 deletions atest/robot/output/source_and_lineno_output.robot
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
Resource atest_resource.robot
Suite Setup Run Tests ${EMPTY} misc/suites/subsuites2

*** Variables ***
${SOURCE} ${{pathlib.Path('${DATADIR}/misc/suites/subsuites2')}}

*** Test Cases ***
Suite source and test lineno in output after execution
Source info should be correct
Expand All @@ -13,10 +16,9 @@ Suite source and test lineno in output after Rebot

*** Keywords ***
Source info should be correct
${source} = Normalize Path ${DATADIR}/misc/suites/subsuites2
Should Be Equal ${SUITE.source} ${source}
Should Be Equal ${SUITE.suites[0].source} ${source}${/}sub.suite.4.robot
Should Be Equal ${SUITE.source} ${SOURCE}
Should Be Equal ${SUITE.suites[0].source} ${SOURCE / 'sub.suite.4.robot'}
Should Be Equal ${SUITE.suites[0].tests[0].lineno} ${2}
Should Be Equal ${SUITE.suites[1].source} ${source}${/}subsuite3.robot
Should Be Equal ${SUITE.suites[1].source} ${SOURCE / 'subsuite3.robot'}
Should Be Equal ${SUITE.suites[1].tests[0].lineno} ${8}
Should Be Equal ${SUITE.suites[1].tests[1].lineno} ${13}
2 changes: 1 addition & 1 deletion atest/robot/parsing/paths_are_not_case_normalized.robot
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Suite name is not case normalized
Should Be Equal ${SUITE.name} suiTe 8

Suite source should not be case normalized
Should End With ${SUITE.source} multiple_suites${/}suiTe_8.robot
Should Be True str($SUITE.source).endswith(r'multiple_suites${/}suiTe_8.robot')

Outputs are not case normalized
Stdout Should Contain ${/}LOG.html
Expand Down
2 changes: 1 addition & 1 deletion src/robot/libdocpkg/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def to_dictionary(self, include_private=False, theme=None):
'type': self.type,
'scope': self.scope,
'docFormat': self.doc_format,
'source': self.source,
'source': str(self.source) if self.source else '',
'lineno': self.lineno,
'tags': list(self.all_tags),
'inits': [init.to_dictionary() for init in self.inits],
Expand Down
2 changes: 1 addition & 1 deletion src/robot/libdocpkg/xmlwriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def _write_start(self, libdoc, writer):

def _add_source_info(self, attrs, item, lib_source=None):
if item.source and item.source != lib_source:
attrs['source'] = item.source
attrs['source'] = str(item.source)
if item.lineno and item.lineno > 0:
attrs['lineno'] = str(item.lineno)

Expand Down
18 changes: 12 additions & 6 deletions src/robot/model/testsuite.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from pathlib import Path

from robot.utils import setter

from .configurer import SuiteConfigurer
Expand All @@ -35,16 +37,16 @@ class TestSuite(ModelObject):
test_class = TestCase #: Internal usage only.
fixture_class = Keyword #: Internal usage only.
repr_args = ('name',)
__slots__ = ['parent', 'source', '_name', 'doc', '_setup', '_teardown', 'rpa',
__slots__ = ['parent', '_name', 'doc', '_setup', '_teardown', 'rpa',
'_my_visitors']

def __init__(self, name='', doc='', metadata=None, source=None, rpa=False,
parent=None):
def __init__(self, name: str = '', doc: str = '', metadata: dict = None,
source: Path = None, rpa: bool = False, parent: 'TestSuite' = None):
self._name = name
self.doc = doc
self.metadata = metadata
self.source = source #: Path to the source file or directory.
self.parent = parent #: Parent suite. ``None`` with the root suite.
self.source = source
self.parent = parent
self.rpa = rpa #: ``True`` when RPA mode is enabled.
self.suites = None
self.tests = None
Expand All @@ -66,6 +68,10 @@ def name(self):
def name(self, name):
self._name = name

@setter
def source(self, source):
return source if isinstance(source, (Path, type(None))) else Path(source)

@property
def longname(self):
"""Suite name prefixed with the long name of the parent suite."""
Expand Down Expand Up @@ -272,7 +278,7 @@ def to_dict(self):
if self.metadata:
data['metadata'] = dict(self.metadata)
if self.source:
data['source'] = self.source
data['source'] = str(self.source)
if self.rpa:
data['rpa'] = self.rpa
if self.has_setup:
Expand Down
25 changes: 14 additions & 11 deletions src/robot/output/listenerarguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,47 +100,50 @@ def _get_extra_attributes(self, suite):
return {'tests': [t.name for t in suite.tests],
'suites': [s.name for s in suite.suites],
'totaltests': suite.test_count,
'source': suite.source or ''}
'source': str(suite.source or '')}


class EndSuiteArguments(StartSuiteArguments):
_attribute_names = ('id', 'longname', 'doc', 'metadata', 'starttime',
'endtime', 'elapsedtime', 'status', 'message')

def _get_extra_attributes(self, suite):
attrs = StartSuiteArguments._get_extra_attributes(self, suite)
attrs = super()._get_extra_attributes(suite)
attrs['statistics'] = suite.stat_message
return attrs


class StartTestArguments(_ListenerArgumentsFromItem):
_attribute_names = ('id', 'longname', 'doc', 'tags', 'lineno', 'source', 'starttime')
_attribute_names = ('id', 'longname', 'doc', 'tags', 'lineno', 'starttime')

def _get_extra_attributes(self, test):
return {'template': test.template or '',
return {'source': str(test.source or ''),
'template': test.template or '',
'originalname': test.data.name}


class EndTestArguments(StartTestArguments):
_attribute_names = ('id', 'longname', 'doc', 'tags', 'lineno', 'source', 'starttime',
_attribute_names = ('id', 'longname', 'doc', 'tags', 'lineno', 'starttime',
'endtime', 'elapsedtime', 'status', 'message')


class StartKeywordArguments(_ListenerArgumentsFromItem):
_attribute_names = ('doc', 'assign', 'tags', 'lineno', 'source', 'type', 'status',
'starttime')
_attribute_names = ('doc', 'assign', 'tags', 'lineno', 'type', 'status', 'starttime')
_type_attributes = {
BodyItem.FOR: ('variables', 'flavor', 'values'),
BodyItem.IF: ('condition',),
BodyItem.ELSE_IF: ('condition'),
BodyItem.EXCEPT: ('patterns', 'pattern_type', 'variable'),
BodyItem.WHILE: ('condition', 'limit'),
BodyItem.RETURN: ('values',),
BodyItem.ITERATION: ('variables',)}
BodyItem.ITERATION: ('variables',)
}

def _get_extra_attributes(self, kw):
args = [a if is_string(a) else safe_str(a) for a in kw.args]
attrs = {'kwname': kw.kwname or '', 'libname': kw.libname or '', 'args': args}
attrs = {'kwname': kw.kwname or '',
'libname': kw.libname or '',
'args': [a if is_string(a) else safe_str(a) for a in kw.args],
'source': str(kw.source or '')}
if kw.type in self._type_attributes:
attrs.update({name: self._get_attribute_value(kw, name)
for name in self._type_attributes[kw.type]
Expand All @@ -149,5 +152,5 @@ def _get_extra_attributes(self, kw):


class EndKeywordArguments(StartKeywordArguments):
_attribute_names = ('doc', 'assign', 'tags', 'lineno', 'source', 'type', 'status',
_attribute_names = ('doc', 'assign', 'tags', 'lineno', 'type', 'status',
'starttime', 'endtime', 'elapsedtime')
4 changes: 3 additions & 1 deletion src/robot/output/xmllogger.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,9 @@ def end_test(self, test):
self._writer.end('test')

def start_suite(self, suite):
attrs = {'id': suite.id, 'name': suite.name, 'source': suite.source}
attrs = {'id': suite.id, 'name': suite.name}
if suite.source:
attrs['source'] = str(suite.source)
self._writer.start('suite', attrs)

def end_suite(self, suite):
Expand Down
23 changes: 16 additions & 7 deletions src/robot/reporting/jsbuildingcontext.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
# limitations under the License.

from contextlib import contextmanager
from os.path import exists, dirname
from pathlib import Path

from robot.output.loggerhelper import LEVELS
from robot.utils import (attribute_escape, get_link_path, html_escape, is_string,
safe_str, timestamp_to_secs)
from robot.utils import (attribute_escape, get_link_path, html_escape, safe_str,
timestamp_to_secs)

from .expandkeywordmatcher import ExpandKeywordMatcher
from .stringcache import StringCache
Expand All @@ -28,8 +28,7 @@ class JsBuildingContext:

def __init__(self, log_path=None, split_log=False, expand_keywords=None,
prune_input=False):
# log_path can be a custom object in unit tests
self._log_dir = dirname(log_path) if is_string(log_path) else None
self._log_dir = self._get_log_dir(log_path)
self._split_log = split_log
self._prune_input = prune_input
self._strings = self._top_level_strings = StringCache()
Expand All @@ -40,9 +39,17 @@ def __init__(self, log_path=None, split_log=False, expand_keywords=None,
self._expand_matcher = ExpandKeywordMatcher(expand_keywords) \
if expand_keywords else None

def _get_log_dir(self, log_path):
# log_path can be a custom object in unit tests
if isinstance(log_path, Path):
return log_path.parent
if isinstance(log_path, str):
return Path(log_path).parent
return None

def string(self, string, escape=True, attr=False):
if escape and string:
if not is_string(string):
if not isinstance(string, str):
string = safe_str(string)
string = (html_escape if not attr else attribute_escape)(string)
return self._strings.add(string)
Expand All @@ -51,8 +58,10 @@ def html(self, string):
return self._strings.add(string, html=True)

def relative_source(self, source):
if isinstance(source, str):
source = Path(source)
rel_source = get_link_path(source, self._log_dir) \
if self._log_dir and source and exists(source) else ''
if self._log_dir and source and source.exists() else ''
return self.string(rel_source)

def timestamp(self, time):
Expand Down
2 changes: 1 addition & 1 deletion src/robot/running/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def end_suite(self, suite):

def set_suite_variables(self, suite):
self.variables['${SUITE_NAME}'] = suite.longname
self.variables['${SUITE_SOURCE}'] = suite.source or ''
self.variables['${SUITE_SOURCE}'] = str(suite.source or '')
self.variables['${SUITE_DOCUMENTATION}'] = suite.doc
self.variables['${SUITE_METADATA}'] = suite.metadata.copy()

Expand Down
6 changes: 3 additions & 3 deletions src/robot/running/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def _import_resource(self, import_setting, overwrite=False):
self._kw_store.resources[path] = user_library
self._handle_imports(resource.imports)
LOGGER.imported("Resource", user_library.name,
importer=import_setting.source,
importer=str(import_setting.source),
source=path)
else:
LOGGER.info(f"Resource file '{path}' already imported by "
Expand All @@ -112,7 +112,7 @@ def _import_variables(self, import_setting, overwrite=False):
self.variables.set_from_file(path, args, overwrite)
LOGGER.imported("Variables", os.path.basename(path),
args=list(args),
importer=import_setting.source,
importer=str(import_setting.source),
source=path)
else:
msg = f"Variable file '{path}'"
Expand All @@ -135,7 +135,7 @@ def _import_library(self, import_setting, notify=True):
LOGGER.imported("Library", lib.name,
args=list(import_setting.args),
originalname=lib.orig_name,
importer=import_setting.source,
importer=str(import_setting.source),
source=lib.source)
self._kw_store.libraries[lib.name] = lib
lib.start_suite()
Expand Down
6 changes: 3 additions & 3 deletions src/robot/testdoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
from robot.htmldata import HtmlFileWriter, ModelWriter, JsonWriter, TESTDOC
from robot.running import TestSuiteBuilder
from robot.utils import (abspath, Application, file_writer, get_link_path,
html_escape, html_format, is_string, secs_to_timestr,
html_escape, html_format, is_list_like, secs_to_timestr,
seq2str2, timestr_to_secs, unescape)


Expand Down Expand Up @@ -130,7 +130,7 @@ def _write_test_doc(self, suite, outfile, title):

def TestSuiteFactory(datasources, **options):
settings = RobotSettings(options)
if is_string(datasources):
if not is_list_like(datasources):
datasources = [datasources]
suite = TestSuiteBuilder(process_curdir=False).build(*datasources)
suite.configure(**settings.suite_config)
Expand Down Expand Up @@ -169,7 +169,7 @@ def convert(self, suite):

def _convert_suite(self, suite):
return {
'source': suite.source or '',
'source': str(suite.source or ''),
'relativeSource': self._get_relative_source(suite.source),
'id': suite.id,
'name': self._escape(suite.name),
Expand Down
2 changes: 1 addition & 1 deletion src/robot/utils/markupwriters.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def _escape(self, text):

def element(self, name, content=None, attrs=None, escape=True, newline=True):
if content:
_MarkupWriter.element(self, name, content, attrs, escape, newline)
super().element(name, content, attrs, escape, newline)
else:
self._self_closing_element(name, attrs, newline)

Expand Down
3 changes: 2 additions & 1 deletion utest/model/test_testcase.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import unittest
import warnings
from pathlib import Path

from robot.utils.asserts import (assert_equal, assert_false, assert_not_equal, assert_raises,
assert_raises_with_msg, assert_true)
Expand Down Expand Up @@ -31,7 +32,7 @@ def test_source(self):
assert_equal(test.source, None)
suite.tests.append(test)
suite.source = '/unit/tests'
assert_equal(test.source, '/unit/tests')
assert_equal(test.source, Path('/unit/tests'))

def test_setup(self):
assert_equal(self.test.setup.__class__, Keyword)
Expand Down
12 changes: 6 additions & 6 deletions utest/reporting/test_jsmodelbuilders.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import base64
import unittest
import zlib
from os.path import abspath, basename, dirname, join
from pathlib import Path

from robot.utils.asserts import assert_equal, assert_true
from robot.result import Keyword, Message, TestCase, TestSuite
Expand All @@ -14,7 +14,7 @@
from robot.reporting.stringcache import StringIndex


CURDIR = dirname(abspath(__file__))
CURDIR = Path(__file__).resolve().parent


def decode_string(string):
Expand Down Expand Up @@ -48,9 +48,9 @@ def test_suite_with_values(self):

def test_relative_source(self):
self._verify_suite(TestSuite(source='non-existing'), source='non-existing')
source = join(CURDIR, 'test_jsmodelbuilders.py')
self._verify_suite(TestSuite(source=source), source=source,
relsource=basename(source))
source = CURDIR / 'test_jsmodelbuilders.py'
self._verify_suite(TestSuite(name='x', source=source),
name='x', source=str(source), relsource=str(source.name))

def test_suite_html_formatting(self):
self._verify_suite(TestSuite(name='*xxx*', doc='*bold* <&>',
Expand Down Expand Up @@ -233,7 +233,7 @@ def _verify_min_message_level(self, expected):
assert_equal(self.context.min_level, expected)

def _build_and_verify(self, builder_class, item, *expected):
self.context = JsBuildingContext(log_path=join(CURDIR, 'log.html'))
self.context = JsBuildingContext(log_path=CURDIR / 'log.html')
model = builder_class(self.context).build(item)
self._verify_mapped(model, self.context.strings, expected)
return expected
Expand Down

0 comments on commit 92148c8

Please sign in to comment.