'
40 | else:
41 | __version__ = get_version()
42 |
43 | __all__ = ['execute_manual_step', 'get_value_from_user',
44 | 'get_selection_from_user', 'pause_execution']
45 |
46 |
47 | def pause_execution(message='Test execution paused. Press OK to continue.'):
48 | """Pauses test execution until user clicks `Ok` button.
49 |
50 | `message` is the message shown in the dialog.
51 | """
52 | MessageDialog(message).show()
53 |
54 |
55 | def execute_manual_step(message, default_error=''):
56 | """Pauses test execution until user sets the keyword status.
57 |
58 | User can select 'PASS' or 'FAIL', and in the latter case an additional
59 | dialog is opened for defining the error message.
60 |
61 | `message` is the instruction shown in the initial dialog and
62 | `default_error` is the default value shown in the possible error message
63 | dialog.
64 | """
65 | if not PassFailDialog(message).show():
66 | msg = get_value_from_user('Give error message:', default_error)
67 | raise AssertionError(msg)
68 |
69 |
70 | def get_value_from_user(message, default_value=''):
71 | """Pauses test execution and asks user to input a value.
72 |
73 | `message` is the instruction shown in the dialog and `default_value` is
74 | the possible default value shown in the input field. Selecting 'Cancel'
75 | fails the keyword.
76 | """
77 | return _validate_user_input(InputDialog(message, default_value))
78 |
79 |
80 | def get_selection_from_user(message, *values):
81 | """Pauses test execution and asks user to select a value.
82 |
83 | `message` is the instruction shown in the dialog and `values` are
84 | the options given to the user. Selecting 'Cancel' fails the keyword.
85 | """
86 | return _validate_user_input(SelectionDialog(message, values))
87 |
88 |
89 | def _validate_user_input(dialog):
90 | value = dialog.show()
91 | if value is None:
92 | raise RuntimeError('No value provided by user')
93 | return value
94 |
--------------------------------------------------------------------------------
/lib/robot/libraries/Easter.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | def none_shall_pass(who):
16 | if who is not None:
17 | raise AssertionError('None shall pass!')
18 | print '*HTML* '
19 |
--------------------------------------------------------------------------------
/lib/robot/libraries/Reserved.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 |
16 | RESERVED_KEYWORDS = [ 'for', 'while', 'break', 'continue', 'end',
17 | 'if', 'else', 'elif', 'else if', 'return' ]
18 |
19 |
20 | class Reserved:
21 |
22 | ROBOT_LIBRARY_SCOPE = 'GLOBAL'
23 |
24 | def get_keyword_names(self):
25 | return RESERVED_KEYWORDS
26 |
27 | def run_keyword(self, name, args):
28 | raise Exception("'%s' is a reserved keyword" % name.title())
29 |
30 |
--------------------------------------------------------------------------------
/lib/robot/libraries/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | """Contains Robot Framework standard test libraries."""
16 |
--------------------------------------------------------------------------------
/lib/robot/libraries/dialogs_ipy.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 |
16 | class _AbstractWinformsDialog:
17 |
18 | def __init__(self):
19 | raise RuntimeError('This keyword is not yet implemented with IronPython')
20 |
21 |
22 | class MessageDialog(_AbstractWinformsDialog):
23 |
24 | def __init__(self, message):
25 | _AbstractWinformsDialog.__init__(self)
26 |
27 |
28 | class InputDialog(_AbstractWinformsDialog):
29 |
30 | def __init__(self, message, default):
31 | _AbstractWinformsDialog.__init__(self)
32 |
33 |
34 | class SelectionDialog(_AbstractWinformsDialog):
35 |
36 | def __init__(self, message, options):
37 | _AbstractWinformsDialog.__init__(self)
38 |
39 |
40 | class PassFailDialog(_AbstractWinformsDialog):
41 |
42 | def __init__(self, message):
43 | _AbstractWinformsDialog.__init__(self)
44 |
--------------------------------------------------------------------------------
/lib/robot/libraries/dialogs_jy.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import time
16 | from javax.swing import JOptionPane
17 | from javax.swing.JOptionPane import PLAIN_MESSAGE, UNINITIALIZED_VALUE, \
18 | YES_NO_OPTION, OK_CANCEL_OPTION, DEFAULT_OPTION
19 |
20 |
21 | class _SwingDialog(object):
22 |
23 | def __init__(self, pane):
24 | self._pane = pane
25 |
26 | def show(self):
27 | self._show_dialog(self._pane)
28 | return self._get_value(self._pane)
29 |
30 | def _show_dialog(self, pane):
31 | dialog = pane.createDialog(None, 'Robot Framework')
32 | dialog.setModal(False)
33 | dialog.setAlwaysOnTop(True)
34 | dialog.show()
35 | while dialog.isShowing():
36 | time.sleep(0.2)
37 | dialog.dispose()
38 |
39 | def _get_value(self, pane):
40 | value = pane.getInputValue()
41 | return value if value != UNINITIALIZED_VALUE else None
42 |
43 |
44 | class MessageDialog(_SwingDialog):
45 |
46 | def __init__(self, message):
47 | pane = JOptionPane(message, PLAIN_MESSAGE, DEFAULT_OPTION)
48 | _SwingDialog.__init__(self, pane)
49 |
50 |
51 | class InputDialog(_SwingDialog):
52 |
53 | def __init__(self, message, default):
54 | pane = JOptionPane(message, PLAIN_MESSAGE, OK_CANCEL_OPTION)
55 | pane.setWantsInput(True)
56 | pane.setInitialSelectionValue(default)
57 | _SwingDialog.__init__(self, pane)
58 |
59 |
60 | class SelectionDialog(_SwingDialog):
61 |
62 | def __init__(self, message, options):
63 | pane = JOptionPane(message, PLAIN_MESSAGE, OK_CANCEL_OPTION)
64 | pane.setWantsInput(True)
65 | pane.setSelectionValues(options)
66 | _SwingDialog.__init__(self, pane)
67 |
68 |
69 | class PassFailDialog(_SwingDialog):
70 |
71 | def __init__(self, message):
72 | pane = JOptionPane(message, PLAIN_MESSAGE, YES_NO_OPTION,
73 | None, ['PASS', 'FAIL'], 'PASS')
74 | _SwingDialog.__init__(self, pane)
75 |
76 | def _get_value(self, pane):
77 | return pane.getValue() == 'PASS'
78 |
--------------------------------------------------------------------------------
/lib/robot/model/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | """Contains base classes and other generic functionality.
16 |
17 | In RF 2.7 this package is mainly used by :mod:`robot.result` package, but
18 | there is a plan to change also :mod:`robot.running` to use this in RF 2.8.
19 |
20 | This package is considered stable.
21 | """
22 |
23 | from .testsuite import TestSuite
24 | from .testcase import TestCase
25 | from .keyword import Keyword
26 | from .message import Message
27 | from .tags import Tags, TagPatterns
28 | from .criticality import Criticality
29 | from .namepatterns import SuiteNamePatterns, TestNamePatterns
30 | from .visitor import SuiteVisitor, SkipAllVisitor
31 | from .totalstatistics import TotalStatisticsBuilder
32 | from .statistics import Statistics
33 | from .itemlist import ItemList
34 |
--------------------------------------------------------------------------------
/lib/robot/model/criticality.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from .tags import TagPatterns
16 |
17 |
18 | class Criticality(object):
19 |
20 | def __init__(self, critical_tags=None, non_critical_tags=None):
21 | self.critical_tags = TagPatterns(critical_tags)
22 | self.non_critical_tags = TagPatterns(non_critical_tags)
23 |
24 | def tag_is_critical(self, tag):
25 | return self.critical_tags.match(tag)
26 |
27 | def tag_is_non_critical(self, tag):
28 | return self.non_critical_tags.match(tag)
29 |
30 | def test_is_critical(self, test):
31 | if self.critical_tags and not self.critical_tags.match(test.tags):
32 | return False
33 | return not self.non_critical_tags.match(test.tags)
34 |
35 | def __nonzero__(self):
36 | return bool(self.critical_tags or self.non_critical_tags)
37 |
--------------------------------------------------------------------------------
/lib/robot/model/itemlist.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 |
16 | class ItemList(object):
17 | __slots__ = ['_item_class', '_common_attrs', '_items']
18 |
19 | def __init__(self, item_class, common_attrs=None, items=None):
20 | self._item_class = item_class
21 | self._common_attrs = common_attrs
22 | self._items = []
23 | if items:
24 | self.extend(items)
25 |
26 | def create(self, *args, **kwargs):
27 | self.append(self._item_class(*args, **kwargs))
28 | return self._items[-1]
29 |
30 | def append(self, item):
31 | self._check_type_and_set_attrs(item)
32 | self._items.append(item)
33 |
34 | def _check_type_and_set_attrs(self, item):
35 | if not isinstance(item, self._item_class):
36 | raise TypeError("Only '%s' objects accepted, got '%s'"
37 | % (self._item_class.__name__, type(item).__name__))
38 | if self._common_attrs:
39 | for attr in self._common_attrs:
40 | setattr(item, attr, self._common_attrs[attr])
41 |
42 | def extend(self, items):
43 | for item in items:
44 | self._check_type_and_set_attrs(item)
45 | self._items.extend(items)
46 |
47 | def index(self, item):
48 | return self._items.index(item)
49 |
50 | def clear(self):
51 | self._items = []
52 |
53 | def visit(self, visitor):
54 | for item in self:
55 | item.visit(visitor)
56 |
57 | def __iter__(self):
58 | return iter(self._items)
59 |
60 | def __getitem__(self, index):
61 | if isinstance(index, slice):
62 | raise ValueError("'%s' object does not support slicing" % type(self).__name__)
63 | return self._items[index]
64 |
65 | def __len__(self):
66 | return len(self._items)
67 |
68 | def __unicode__(self):
69 | return u'[%s]' % ', '.join(unicode(item) for item in self)
70 |
71 | def __str__(self):
72 | return unicode(self).encode('ASCII', 'replace')
73 |
--------------------------------------------------------------------------------
/lib/robot/model/keyword.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from robot.utils import setter
16 |
17 | from itemlist import ItemList
18 | from message import Message, Messages
19 | from modelobject import ModelObject
20 |
21 |
22 | class Keyword(ModelObject):
23 | __slots__ = ['parent', 'name', 'doc', 'args', 'type', 'timeout']
24 | KEYWORD_TYPE = 'kw'
25 | SETUP_TYPE = 'setup'
26 | TEARDOWN_TYPE = 'teardown'
27 | FOR_LOOP_TYPE = 'for'
28 | FOR_ITEM_TYPE = 'foritem'
29 | message_class = Message
30 |
31 | def __init__(self, name='', doc='', args=None, type='kw', timeout=''):
32 | self.parent = None
33 | self.name = name
34 | self.doc = doc
35 | self.args = args or []
36 | self.type = type
37 | self.timeout = timeout
38 | self.messages = []
39 | self.keywords = []
40 |
41 | @setter
42 | def keywords(self, keywords):
43 | return Keywords(self.__class__, self, keywords)
44 |
45 | @setter
46 | def messages(self, messages):
47 | return Messages(self.message_class, self, messages)
48 |
49 | @property
50 | def id(self):
51 | if not self.parent:
52 | return 'k1'
53 | return '%s-k%d' % (self.parent.id, self.parent.keywords.index(self)+1)
54 |
55 | def visit(self, visitor):
56 | visitor.visit_keyword(self)
57 |
58 |
59 | class Keywords(ItemList):
60 | __slots__ = []
61 |
62 | def __init__(self, keyword_class=Keyword, parent=None, keywords=None):
63 | ItemList.__init__(self, keyword_class, {'parent': parent}, keywords)
64 |
65 | @property
66 | def setup(self):
67 | return self[0] if (self and self[0].type == 'setup') else None
68 |
69 | @property
70 | def teardown(self):
71 | return self[-1] if (self and self[-1].type == 'teardown') else None
72 |
73 | @property
74 | def all(self):
75 | return self
76 |
77 | @property
78 | def normal(self):
79 | for kw in self:
80 | if kw.type not in ('setup', 'teardown'):
81 | yield kw
82 |
--------------------------------------------------------------------------------
/lib/robot/model/message.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from robot.utils import html_escape
16 |
17 | from .itemlist import ItemList
18 | from .modelobject import ModelObject
19 |
20 |
21 | class Message(ModelObject):
22 | __slots__ = ['message', 'level', 'html', 'timestamp', 'parent']
23 |
24 | def __init__(self, message='', level='INFO', html=False, timestamp=None,
25 | parent=None):
26 | self.message = message
27 | self.level = level
28 | self.html = html
29 | self.timestamp = timestamp
30 | self.parent = parent
31 |
32 | @property
33 | def html_message(self):
34 | return self.message if self.html else html_escape(self.message)
35 |
36 | def visit(self, visitor):
37 | visitor.visit_message(self)
38 |
39 | def __unicode__(self):
40 | return self.message
41 |
42 |
43 | class Messages(ItemList):
44 | __slots__ = []
45 |
46 | def __init__(self, message_class=Message, parent=None, messages=None):
47 | ItemList.__init__(self, message_class, {'parent': parent}, messages)
48 |
49 |
--------------------------------------------------------------------------------
/lib/robot/model/metadata.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from robot.utils import NormalizedDict
16 |
17 |
18 | class Metadata(NormalizedDict):
19 |
20 | def __init__(self, initial=None):
21 | NormalizedDict.__init__(self, initial, ignore=['_'])
22 |
23 | def __unicode__(self):
24 | return u'{%s}' % ', '.join('%s: %s' % (k, self[k]) for k in self)
25 |
26 | def __str__(self):
27 | return unicode(self).encode('ASCII', 'replace')
28 |
--------------------------------------------------------------------------------
/lib/robot/model/modelobject.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from robot.utils.setter import SetterAwareType
16 |
17 |
18 | class ModelObject(object):
19 | __slots__ = []
20 | __metaclass__ = SetterAwareType
21 |
22 | def __unicode__(self):
23 | return self.name
24 |
25 | def __str__(self):
26 | return unicode(self).encode('ASCII', 'replace')
27 |
--------------------------------------------------------------------------------
/lib/robot/model/namepatterns.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from robot.utils import MultiMatcher
16 |
17 |
18 | class _NamePatterns(object):
19 |
20 | def __init__(self, patterns=None):
21 | self._matcher = MultiMatcher(patterns, ignore=['_'])
22 |
23 | def match(self, name, longname=None):
24 | return self._match(name) or longname and self._match_longname(longname)
25 |
26 | def _match(self, name):
27 | return self._matcher.match(name)
28 |
29 | def _match_longname(self, name):
30 | raise NotImplementedError
31 |
32 | def __nonzero__(self):
33 | return bool(self._matcher)
34 |
35 |
36 | class SuiteNamePatterns(_NamePatterns):
37 |
38 | def _match_longname(self, name):
39 | while '.' in name:
40 | if self._match(name):
41 | return True
42 | name = name.split('.', 1)[1]
43 | return False
44 |
45 |
46 | class TestNamePatterns(_NamePatterns):
47 |
48 | def _match_longname(self, name):
49 | return self._match(name)
50 |
--------------------------------------------------------------------------------
/lib/robot/model/statistics.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from .totalstatistics import TotalStatisticsBuilder
16 | from .suitestatistics import SuiteStatisticsBuilder
17 | from .tagstatistics import TagStatisticsBuilder
18 | from .visitor import SuiteVisitor
19 |
20 |
21 | class Statistics(object):
22 |
23 | def __init__(self, suite, suite_stat_level=-1, tag_stat_include=None,
24 | tag_stat_exclude=None, tag_stat_combine=None, tag_doc=None,
25 | tag_stat_link=None):
26 | total_builder = TotalStatisticsBuilder()
27 | suite_builder = SuiteStatisticsBuilder(suite_stat_level)
28 | tag_builder = TagStatisticsBuilder(suite.criticality, tag_stat_include,
29 | tag_stat_exclude, tag_stat_combine,
30 | tag_doc, tag_stat_link)
31 | suite.visit(StatisticsBuilder(total_builder, suite_builder, tag_builder))
32 | self.total = total_builder.stats
33 | self.suite = suite_builder.stats
34 | self.tags = tag_builder.stats
35 |
36 | def visit(self, visitor):
37 | visitor.visit_statistics(self)
38 |
39 |
40 | class StatisticsBuilder(SuiteVisitor):
41 |
42 | def __init__(self, total_builder, suite_builder, tag_builder):
43 | self._total_builder = total_builder
44 | self._suite_builder = suite_builder
45 | self._tag_builder = tag_builder
46 |
47 | def start_suite(self, suite):
48 | self._suite_builder.start_suite(suite)
49 |
50 | def end_suite(self, suite):
51 | self._suite_builder.end_suite()
52 |
53 | def visit_test(self, test):
54 | self._total_builder.add_test(test)
55 | self._suite_builder.add_test(test)
56 | self._tag_builder.add_test(test)
57 |
58 | def visit_keyword(self, kw):
59 | pass
60 |
--------------------------------------------------------------------------------
/lib/robot/model/suitestatistics.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from .stats import SuiteStat
16 |
17 |
18 | class SuiteStatistics(object):
19 |
20 | def __init__(self, suite):
21 | self.stat = SuiteStat(suite)
22 | self.suites = []
23 |
24 | def visit(self, visitor):
25 | visitor.visit_suite_statistics(self)
26 |
27 | def __iter__(self):
28 | yield self.stat
29 | for child in self.suites:
30 | for stat in child:
31 | yield stat
32 |
33 |
34 | class SuiteStatisticsBuilder(object):
35 |
36 | def __init__(self, suite_stat_level):
37 | self._suite_stat_level = suite_stat_level
38 | self._stats_stack = []
39 | self.stats = None
40 |
41 | @property
42 | def current(self):
43 | return self._stats_stack[-1] if self._stats_stack else None
44 |
45 | def start_suite(self, suite):
46 | self._stats_stack.append(SuiteStatistics(suite))
47 | if self.stats is None:
48 | self.stats = self.current
49 |
50 | def add_test(self, test):
51 | self.current.stat.add_test(test)
52 |
53 | def end_suite(self):
54 | stats = self._stats_stack.pop()
55 | if self.current:
56 | self.current.stat.add_stat(stats.stat)
57 | if self._is_child_included():
58 | self.current.suites.append(stats)
59 |
60 | def _is_child_included(self):
61 | return self._include_all_levels() or self._below_threshold()
62 |
63 | def _include_all_levels(self):
64 | return self._suite_stat_level == -1
65 |
66 | def _below_threshold(self):
67 | return len(self._stats_stack) < self._suite_stat_level
68 |
--------------------------------------------------------------------------------
/lib/robot/model/tagsetter.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from visitor import SuiteVisitor
16 |
17 |
18 | class TagSetter(SuiteVisitor):
19 |
20 | def __init__(self, add=None, remove=None):
21 | self.add = add
22 | self.remove = remove
23 |
24 | def start_suite(self, suite):
25 | return bool(self)
26 |
27 | def visit_test(self, test):
28 | test.tags.add(self.add)
29 | test.tags.remove(self.remove)
30 |
31 | def visit_keyword(self, keyword):
32 | pass
33 |
34 | def __nonzero__(self):
35 | return bool(self.add or self.remove)
36 |
--------------------------------------------------------------------------------
/lib/robot/model/testcase.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from robot.utils import setter
16 |
17 | from .itemlist import ItemList
18 | from .keyword import Keyword, Keywords
19 | from .modelobject import ModelObject
20 | from .tags import Tags
21 |
22 |
23 | class TestCase(ModelObject):
24 | __slots__ = ['parent', 'name', 'doc', 'timeout']
25 | keyword_class = Keyword
26 |
27 | def __init__(self, name='', doc='', tags=None, timeout=''):
28 | self.parent = None
29 | self.name = name
30 | self.doc = doc
31 | self.tags = tags
32 | self.timeout = timeout
33 | self.keywords = []
34 |
35 | @setter
36 | def tags(self, tags):
37 | return Tags(tags)
38 |
39 | @setter
40 | def keywords(self, keywords):
41 | return Keywords(self.keyword_class, self, keywords)
42 |
43 | @property
44 | def id(self):
45 | if not self.parent:
46 | return 't1'
47 | return '%s-t%d' % (self.parent.id, self.parent.tests.index(self)+1)
48 |
49 | @property
50 | def longname(self):
51 | if not self.parent:
52 | return self.name
53 | return '%s.%s' % (self.parent.longname, self.name)
54 |
55 | @property
56 | def critical(self):
57 | if not self.parent:
58 | return True
59 | return self.parent.criticality.test_is_critical(self)
60 |
61 | def visit(self, visitor):
62 | visitor.visit_test(self)
63 |
64 |
65 | class TestCases(ItemList):
66 | __slots__ = []
67 |
68 | def __init__(self, test_class=TestCase, parent=None, tests=None):
69 | ItemList.__init__(self, test_class, {'parent': parent}, tests)
70 |
--------------------------------------------------------------------------------
/lib/robot/model/totalstatistics.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from .stats import TotalStat
16 | from .visitor import SuiteVisitor
17 |
18 |
19 | class TotalStatistics(object):
20 |
21 | def __init__(self):
22 | self.critical = TotalStat('Critical Tests')
23 | self.all = TotalStat('All Tests')
24 |
25 | def visit(self, visitor):
26 | visitor.visit_total_statistics(self)
27 |
28 | def __iter__(self):
29 | return iter([self.critical, self.all])
30 |
31 | @property
32 | def message(self):
33 | ctotal, cend, cpass, cfail = self._get_counts(self.critical)
34 | atotal, aend, apass, afail = self._get_counts(self.all)
35 | return ('%d critical test%s, %d passed, %d failed\n'
36 | '%d test%s total, %d passed, %d failed'
37 | % (ctotal, cend, cpass, cfail, atotal, aend, apass, afail))
38 |
39 | def _get_counts(self, stat):
40 | ending = 's' if stat.total != 1 else ''
41 | return stat.total, ending, stat.passed, stat.failed
42 |
43 |
44 | class TotalStatisticsBuilder(SuiteVisitor):
45 |
46 | def __init__(self, suite=None):
47 | self.stats = TotalStatistics()
48 | if suite:
49 | suite.visit(self)
50 |
51 | def add_test(self, test):
52 | self.stats.all.add_test(test)
53 | if test.critical:
54 | self.stats.critical.add_test(test)
55 |
56 | def visit_test(self, test):
57 | self.add_test(test)
58 |
59 | def visit_keyword(self, kw):
60 | pass
61 |
--------------------------------------------------------------------------------
/lib/robot/model/visitor.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | class SuiteVisitor(object):
16 |
17 | def visit_suite(self, suite):
18 | if self.start_suite(suite) is not False:
19 | suite.keywords.visit(self)
20 | suite.suites.visit(self)
21 | suite.tests.visit(self)
22 | self.end_suite(suite)
23 |
24 | def start_suite(self, suite):
25 | pass
26 |
27 | def end_suite(self, suite):
28 | pass
29 |
30 | def visit_test(self, test):
31 | if self.start_test(test) is not False:
32 | test.keywords.visit(self)
33 | self.end_test(test)
34 |
35 | def start_test(self, test):
36 | pass
37 |
38 | def end_test(self, test):
39 | pass
40 |
41 | def visit_keyword(self, kw):
42 | if self.start_keyword(kw) is not False:
43 | kw.keywords.visit(self)
44 | kw.messages.visit(self)
45 | self.end_keyword(kw)
46 |
47 | def start_keyword(self, keyword):
48 | pass
49 |
50 | def end_keyword(self, keyword):
51 | pass
52 |
53 | def visit_message(self, msg):
54 | if self.start_message(msg) is not False:
55 | self.end_message(msg)
56 |
57 | def start_message(self, msg):
58 | pass
59 |
60 | def end_message(self, msg):
61 | pass
62 |
63 |
64 | class SkipAllVisitor(SuiteVisitor):
65 |
66 | def visit_suite(self, suite):
67 | pass
68 |
69 | def visit_keyword(self, kw):
70 | pass
71 |
72 | def visit_test(self, test):
73 | pass
74 |
75 | def visit_message(self, msg):
76 | pass
77 |
--------------------------------------------------------------------------------
/lib/robot/output/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | """TImplements runtime logging and listener interface.
16 |
17 | This package is likely to change in RF 2.8.
18 | """
19 |
20 | from .output import Output
21 | from .logger import LOGGER
22 | from .xmllogger import XmlLogger
23 | from .loggerhelper import LEVELS, Message
24 |
--------------------------------------------------------------------------------
/lib/robot/output/filelogger.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from robot.errors import DataError
16 |
17 | from .loggerhelper import AbstractLogger
18 |
19 |
20 | class FileLogger(AbstractLogger):
21 |
22 | def __init__(self, path, level):
23 | AbstractLogger.__init__(self, level)
24 | self._writer = self._get_writer(path) # unit test hook
25 |
26 | def _get_writer(self, path):
27 | try:
28 | return open(path, 'w')
29 | except EnvironmentError, err:
30 | raise DataError(err.strerror)
31 |
32 | def message(self, msg):
33 | if self._is_logged(msg.level) and not self._writer.closed:
34 | entry = '%s | %s | %s\n' % (msg.timestamp, msg.level.ljust(5),
35 | msg.message)
36 | self._writer.write(entry.encode('UTF-8'))
37 |
38 | def start_suite(self, suite):
39 | self.info("Started test suite '%s'" % suite.name)
40 |
41 | def end_suite(self, suite):
42 | self.info("Ended test suite '%s'" % suite.name)
43 |
44 | def start_test(self, test):
45 | self.info("Started test case '%s'" % test.name)
46 |
47 | def end_test(self, test):
48 | self.info("Ended test case '%s'" % test.name)
49 |
50 | def start_keyword(self, kw):
51 | self.debug(lambda: "Started keyword '%s'" % kw.name)
52 |
53 | def end_keyword(self, kw):
54 | self.debug(lambda: "Ended keyword '%s'" % kw.name)
55 |
56 | def output_file(self, name, path):
57 | self.info('%s: %s' % (name, path))
58 |
59 | def close(self):
60 | self._writer.close()
61 |
--------------------------------------------------------------------------------
/lib/robot/output/output.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from robot.common.statistics import Statistics
16 |
17 | from .listeners import Listeners
18 | from .logger import LOGGER
19 | from .loggerhelper import AbstractLogger
20 | from .debugfile import DebugFile
21 | from .xmllogger import XmlLogger
22 |
23 |
24 | class Output(AbstractLogger):
25 |
26 | def __init__(self, settings):
27 | AbstractLogger.__init__(self)
28 | self._xmllogger = XmlLogger(settings['Output'], settings['LogLevel'])
29 | self._register_loggers(settings['Listeners'], settings['DebugFile'])
30 | self._settings = settings
31 |
32 | def _register_loggers(self, listeners, debugfile):
33 | LOGGER.register_context_changing_logger(self._xmllogger)
34 | for logger in Listeners(listeners), DebugFile(debugfile):
35 | if logger: LOGGER.register_logger(logger)
36 | LOGGER.disable_message_cache()
37 |
38 | def close(self, suite):
39 | stats = Statistics(suite, self._settings['SuiteStatLevel'],
40 | self._settings['TagStatInclude'],
41 | self._settings['TagStatExclude'],
42 | self._settings['TagStatCombine'],
43 | self._settings['TagDoc'],
44 | self._settings['TagStatLink'])
45 | stats.serialize(self._xmllogger)
46 | self._xmllogger.close()
47 | LOGGER.unregister_logger(self._xmllogger)
48 | LOGGER.output_file('Output', self._settings['Output'])
49 |
50 | def start_suite(self, suite):
51 | LOGGER.start_suite(suite)
52 |
53 | def end_suite(self, suite):
54 | LOGGER.end_suite(suite)
55 |
56 | def start_test(self, test):
57 | LOGGER.start_test(test)
58 |
59 | def end_test(self, test):
60 | LOGGER.end_test(test)
61 |
62 | def start_keyword(self, kw):
63 | LOGGER.start_keyword(kw)
64 |
65 | def end_keyword(self, kw):
66 | LOGGER.end_keyword(kw)
67 |
68 | def message(self, msg):
69 | LOGGER.log_message(msg)
70 |
71 | def set_log_level(self, level):
72 | # TODO: Module structure should be cleaned up to prevent cyclic imports
73 | from .pyloggingconf import set_level
74 | set_level(level)
75 | return self._xmllogger.set_log_level(level)
76 |
77 |
--------------------------------------------------------------------------------
/lib/robot/output/pyloggingconf.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import logging
16 |
17 | from robot.api import logger
18 | from robot import utils
19 |
20 | LEVELS = {'TRACE': logging.NOTSET,
21 | 'DEBUG': logging.DEBUG,
22 | 'INFO': logging.INFO,
23 | 'WARN': logging.WARNING}
24 |
25 |
26 | def initialize(level):
27 | logging.raiseExceptions = False
28 | logging.getLogger().addHandler(RobotHandler())
29 | set_level(level)
30 |
31 |
32 | def set_level(level):
33 | try:
34 | level = LEVELS[level.upper()]
35 | except KeyError:
36 | return
37 | logging.getLogger().setLevel(level)
38 |
39 |
40 | class RobotHandler(logging.Handler):
41 |
42 | def emit(self, record):
43 | message, error = self._get_message(record)
44 | method = self._get_logger_method(record.levelno)
45 | method(message)
46 | if error:
47 | logger.debug(error)
48 |
49 | def _get_message(self, record):
50 | try:
51 | return record.getMessage(), None
52 | except:
53 | message = 'Failed to log following message properly: %s' \
54 | % utils.unic(record.msg)
55 | error = '\n'.join(utils.get_error_details())
56 | return message, error
57 |
58 | def _get_logger_method(self, level):
59 | if level >= logging.WARNING:
60 | return logger.warn
61 | if level >= logging.INFO:
62 | return logger.info
63 | if level >= logging.DEBUG:
64 | return logger.debug
65 | return logger.trace
66 |
--------------------------------------------------------------------------------
/lib/robot/output/stdoutlogsplitter.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import re
16 |
17 | from robot import utils
18 |
19 | from .loggerhelper import Message, LEVELS
20 |
21 |
22 | class StdoutLogSplitter(object):
23 | """Splits messages logged through stdout (or stderr) into Message objects"""
24 |
25 | _split_from_levels = re.compile('^(?:\*'
26 | '(%s|HTML)' # Level
27 | '(:\d+(?:\.\d+)?)?' # Optional timestamp
28 | '\*)' % '|'.join(LEVELS), re.MULTILINE)
29 |
30 | def __init__(self, output):
31 | self._messages = list(self._get_messages(output.strip()))
32 |
33 | def _get_messages(self, output):
34 | for level, timestamp, msg in self._split_output(output):
35 | if timestamp:
36 | timestamp = self._format_timestamp(timestamp[1:])
37 | yield Message(msg.strip(), level, timestamp=timestamp)
38 |
39 | def _split_output(self, output):
40 | tokens = self._split_from_levels.split(output)
41 | tokens = self._add_initial_level_and_time_if_needed(tokens)
42 | for i in xrange(0, len(tokens), 3):
43 | yield tokens[i:i+3]
44 |
45 | def _add_initial_level_and_time_if_needed(self, tokens):
46 | if self._output_started_with_level(tokens):
47 | return tokens[1:]
48 | return ['INFO', None] + tokens
49 |
50 | def _output_started_with_level(self, tokens):
51 | return tokens[0] == ''
52 |
53 | def _format_timestamp(self, millis):
54 | return utils.format_time(float(millis)/1000, millissep='.')
55 |
56 | def __iter__(self):
57 | return iter(self._messages)
58 |
--------------------------------------------------------------------------------
/lib/robot/parsing/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | """Implements parsing of test data files.
16 |
17 | Classes :class:`~.model.TestCaseFile`, :class:`~.model.TestDataDirectory` and
18 | :class:`~.model.ResourceFile` represented parsed test data. These can be
19 | modified and saved back to disk. In addition, a convenience factory function
20 | :func:`~.model.TestData` can be used to parse file or directory to a
21 | corresponding object.
22 |
23 | This package is considered stable.
24 |
25 | Example
26 | -------
27 |
28 | .. code-block:: python
29 |
30 | from robot.parsing import TestCaseFile
31 |
32 | suite = TestCaseFile(source='path/to/tests.html').populate()
33 | print 'Suite: ', suite.name
34 | for test in suite.testcase_table:
35 | print test.name
36 | """
37 |
38 | from .model import TestData, TestCaseFile, TestDataDirectory, ResourceFile
39 | from . import populators
40 |
41 | VALID_EXTENSIONS = tuple(populators.READERS)
42 |
43 | def disable_curdir_processing(method):
44 | """Decorator to disable processing `${CURDIR}` variable."""
45 | def decorated(*args, **kwargs):
46 | original = populators.PROCESS_CURDIR
47 | populators.PROCESS_CURDIR = False
48 | try:
49 | return method(*args, **kwargs)
50 | finally:
51 | populators.PROCESS_CURDIR = original
52 | return decorated
53 |
--------------------------------------------------------------------------------
/lib/robot/parsing/comments.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 |
16 | class CommentCache(object):
17 |
18 | def __init__(self):
19 | self._comments = []
20 |
21 | def add(self, comment):
22 | self._comments.append(comment)
23 |
24 | def consume_with(self, function):
25 | map(function, self._comments)
26 | self.__init__()
27 |
28 |
29 | class Comments(object):
30 |
31 | def __init__(self):
32 | self._comments = []
33 |
34 | def add(self, row):
35 | if row.comments:
36 | self._comments.extend(c.strip() for c in row.comments if c.strip())
37 |
38 | @property
39 | def value(self):
40 | return self._comments
41 |
--------------------------------------------------------------------------------
/lib/robot/parsing/restreader.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import tempfile
16 | import os
17 |
18 | from robot.errors import DataError
19 |
20 | from .htmlreader import HtmlReader
21 |
22 |
23 | def RestReader():
24 | try:
25 | from docutils.core import publish_cmdline
26 | from docutils.parsers.rst import directives
27 | except ImportError:
28 | raise DataError("Using reStructuredText test data requires having "
29 | "'docutils' module installed.")
30 |
31 | # Ignore custom sourcecode directives at least we use in reST sources.
32 | # See e.g. ug2html.py for an example how custom directives are created.
33 | ignorer = lambda *args: []
34 | ignorer.content = 1
35 | directives.register_directive('sourcecode', ignorer)
36 |
37 | class RestReader(HtmlReader):
38 |
39 | def read(self, rstfile, rawdata):
40 | htmlpath = self._rest_to_html(rstfile.name)
41 | htmlfile = None
42 | try:
43 | htmlfile = open(htmlpath, 'rb')
44 | return HtmlReader.read(self, htmlfile, rawdata)
45 | finally:
46 | if htmlfile:
47 | htmlfile.close()
48 | os.remove(htmlpath)
49 |
50 | def _rest_to_html(self, rstpath):
51 | filedesc, htmlpath = tempfile.mkstemp('.html')
52 | os.close(filedesc)
53 | publish_cmdline(writer_name='html', argv=[rstpath, htmlpath])
54 | return htmlpath
55 |
56 | return RestReader()
57 |
--------------------------------------------------------------------------------
/lib/robot/parsing/tsvreader.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from codecs import BOM_UTF8
16 |
17 |
18 | NBSP = u'\xA0'
19 |
20 |
21 | class TsvReader:
22 |
23 | def read(self, tsvfile, populator):
24 | process = False
25 | for index, row in enumerate(tsvfile.readlines()):
26 | row = self._decode_row(row, index == 0)
27 | cells = [self._process(cell) for cell in self.split_row(row)]
28 | name = cells and cells[0].strip() or ''
29 | if name.startswith('*') and \
30 | populator.start_table([c.replace('*','') for c in cells]):
31 | process = True
32 | elif process:
33 | populator.add(cells, linenumber=index+1)
34 | populator.eof()
35 |
36 | def _decode_row(self, row, is_first):
37 | if is_first and row.startswith(BOM_UTF8):
38 | row = row[len(BOM_UTF8):]
39 | row = row.decode('UTF-8')
40 | if NBSP in row:
41 | row = row.replace(NBSP, ' ')
42 | return row.rstrip()
43 |
44 | @classmethod
45 | def split_row(cls, row):
46 | return row.split('\t')
47 |
48 | def _process(self, cell):
49 | if len(cell) > 1 and cell[0] == cell[-1] == '"':
50 | cell = cell[1:-1].replace('""','"')
51 | return cell
52 |
--------------------------------------------------------------------------------
/lib/robot/parsing/txtreader.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import re
16 |
17 | from .tsvreader import TsvReader
18 |
19 |
20 | class TxtReader(TsvReader):
21 | _space_splitter = re.compile(' {2,}')
22 | _pipe_splitter = re.compile(' \|(?= )')
23 |
24 | @classmethod
25 | def split_row(cls, row):
26 | if '\t' in row:
27 | row = row.replace('\t', ' ')
28 | if not row.startswith('| '):
29 | return cls._space_splitter.split(row)
30 | row = row[1:-1] if row.endswith(' |') else row[1:]
31 | return [cell.strip() for cell in cls._pipe_splitter.split(row)]
32 |
33 | def _process(self, cell):
34 | return cell
35 |
--------------------------------------------------------------------------------
/lib/robot/pythonpathsetter.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | """Module that adds directories needed by Robot to sys.path when imported."""
16 |
17 | import os
18 | import sys
19 | import fnmatch
20 | from os.path import abspath, dirname, join
21 |
22 | ROBOTDIR = dirname(abspath(__file__))
23 |
24 | def add_path(path, end=False):
25 | if not end:
26 | remove_path(path)
27 | sys.path.insert(0, path)
28 | elif not any(fnmatch.fnmatch(p, path) for p in sys.path):
29 | sys.path.append(path)
30 |
31 | def remove_path(path):
32 | sys.path = [p for p in sys.path if not fnmatch.fnmatch(p, path)]
33 |
34 |
35 | # When, for example, robot/run.py is executed as a script, the directory
36 | # containing the robot module is not added to sys.path automatically but
37 | # the robot directory itself is. Former is added to allow importing
38 | # the module and the latter removed to prevent accidentally importing
39 | # internal modules directly.
40 | add_path(dirname(ROBOTDIR))
41 | remove_path(ROBOTDIR)
42 |
43 | # Default library search locations.
44 | add_path(join(ROBOTDIR, 'libraries'))
45 | add_path('.', end=True)
46 |
47 | # Support libraries/resources in PYTHONPATH also with Jython and IronPython.
48 | for item in os.getenv('PYTHONPATH', '').split(os.pathsep):
49 | add_path(abspath(item), end=True)
50 |
51 |
--------------------------------------------------------------------------------
/lib/robot/reporting/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | """Implements report and log file generation.
16 |
17 | This package is considered stable.
18 | """
19 |
20 | from .resultwriter import ResultWriter
21 |
--------------------------------------------------------------------------------
/lib/robot/reporting/jsbuildingcontext.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from contextlib import contextmanager
16 | import os.path
17 |
18 | from robot.output.loggerhelper import LEVELS
19 | from robot.utils import (html_escape, html_format, get_link_path,
20 | timestamp_to_secs)
21 |
22 | from .stringcache import StringCache
23 |
24 |
25 | class JsBuildingContext(object):
26 |
27 | def __init__(self, log_path=None, split_log=False, prune_input=False):
28 | # log_path can be a custom object in unit tests
29 | self._log_dir = os.path.dirname(log_path) \
30 | if isinstance(log_path, basestring) else None
31 | self._split_log = split_log
32 | self._prune_input = prune_input
33 | self._strings = self._top_level_strings = StringCache()
34 | self.basemillis = None
35 | self.split_results = []
36 | self.min_level = 'NONE'
37 | self._msg_links = {}
38 |
39 | def string(self, string, escape=True):
40 | if escape and string: # string can, but should not, be None
41 | string = html_escape(string)
42 | return self._strings.add(string)
43 |
44 | def html(self, string):
45 | return self.string(html_format(string), escape=False)
46 |
47 | def relative_source(self, source):
48 | rel_source = get_link_path(source, self._log_dir) \
49 | if self._log_dir and source and os.path.exists(source) else ''
50 | return self.string(rel_source)
51 |
52 | def timestamp(self, time):
53 | if not time:
54 | return None
55 | # Must use `long` due to http://ironpython.codeplex.com/workitem/31549
56 | millis = long(round(timestamp_to_secs(time) * 1000))
57 | if self.basemillis is None:
58 | self.basemillis = millis
59 | return millis - self.basemillis
60 |
61 | def message_level(self, level):
62 | if LEVELS[level] < LEVELS[self.min_level]:
63 | self.min_level = level
64 |
65 | def create_link_target(self, msg):
66 | id = self._top_level_strings.add(msg.parent.id)
67 | self._msg_links[self._link_key(msg)] = id
68 |
69 | def link(self, msg):
70 | return self._msg_links.get(self._link_key(msg))
71 |
72 | def _link_key(self, msg):
73 | return (msg.message, msg.level, msg.timestamp)
74 |
75 | @property
76 | def strings(self):
77 | return self._strings.dump()
78 |
79 | def start_splitting_if_needed(self, split=False):
80 | if self._split_log and split:
81 | self._strings = StringCache()
82 | return True
83 | return False
84 |
85 | def end_splitting(self, model):
86 | self.split_results.append((model, self.strings))
87 | self._strings = self._top_level_strings
88 | return len(self.split_results)
89 |
90 | @contextmanager
91 | def prune_input(self, *items):
92 | yield
93 | if self._prune_input:
94 | for item in items:
95 | item.clear()
96 |
--------------------------------------------------------------------------------
/lib/robot/reporting/logreportwriters.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from __future__ import with_statement
16 | from os.path import basename, splitext
17 | import codecs
18 |
19 | from robot.htmldata import HtmlFileWriter, ModelWriter, LOG, REPORT
20 | from robot.utils import utf8open
21 |
22 | from .jswriter import JsResultWriter, SplitLogWriter
23 |
24 |
25 | class _LogReportWriter(object):
26 |
27 | def __init__(self, js_model):
28 | self._js_model = js_model
29 |
30 | def _write_file(self, path, config, template):
31 | outfile = codecs.open(path, 'wb', encoding='UTF-8') \
32 | if isinstance(path, basestring) else path # unit test hook
33 | with outfile:
34 | model_writer = RobotModelWriter(outfile, self._js_model, config)
35 | writer = HtmlFileWriter(outfile, model_writer)
36 | writer.write(template)
37 |
38 |
39 | class LogWriter(_LogReportWriter):
40 |
41 | def write(self, path, config):
42 | self._write_file(path, config, LOG)
43 | if self._js_model.split_results:
44 | self._write_split_logs(splitext(path)[0])
45 |
46 | def _write_split_logs(self, base):
47 | for index, (keywords, strings) in enumerate(self._js_model.split_results):
48 | index += 1 # enumerate accepts start index only in Py 2.6+
49 | self._write_split_log(index, keywords, strings, '%s-%d.js' % (base, index))
50 |
51 | def _write_split_log(self, index, keywords, strings, path):
52 | with utf8open(path, 'wb') as outfile:
53 | writer = SplitLogWriter(outfile)
54 | writer.write(keywords, strings, index, basename(path))
55 |
56 |
57 | class ReportWriter(_LogReportWriter):
58 |
59 | def write(self, path, config):
60 | self._write_file(path, config, REPORT)
61 |
62 |
63 | class RobotModelWriter(ModelWriter):
64 |
65 | def __init__(self, output, model, config):
66 | self._output = output
67 | self._model = model
68 | self._config = config
69 |
70 | def write(self, line):
71 | JsResultWriter(self._output).write(self._model, self._config)
72 |
--------------------------------------------------------------------------------
/lib/robot/reporting/outputwriter.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from robot.output.xmllogger import XmlLogger
16 | from robot.result.visitor import ResultVisitor
17 |
18 |
19 | # TODO: Unify XmlLogger and ResultVisitor APIs.
20 | # Perhaps XmlLogger could be ResultVisitor.
21 |
22 |
23 | class OutputWriter(XmlLogger, ResultVisitor):
24 |
25 | def __init__(self, output):
26 | XmlLogger.__init__(self, output, generator='Rebot')
27 |
28 | def start_message(self, msg):
29 | self._write_message(msg)
30 |
31 | def close(self):
32 | self._writer.end('robot')
33 | self._writer.close()
34 |
35 | def start_errors(self, errors):
36 | XmlLogger.start_errors(self)
37 |
38 | def end_errors(self, errors):
39 | XmlLogger.end_errors(self)
40 |
41 | def end_result(self, result):
42 | self.close()
43 |
44 | start_total_statistics = XmlLogger.start_total_stats
45 | start_tag_statistics = XmlLogger.start_tag_stats
46 | start_suite_statistics = XmlLogger.start_suite_stats
47 | end_total_statistics = XmlLogger.end_total_stats
48 | end_tag_statistics = XmlLogger.end_tag_stats
49 | end_suite_statistics = XmlLogger.end_suite_stats
50 |
51 | def visit_stat(self, stat):
52 | self._writer.element('stat', stat.name,
53 | stat.get_attributes(values_as_strings=True))
54 |
--------------------------------------------------------------------------------
/lib/robot/reporting/stringcache.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from operator import itemgetter
16 |
17 | from robot.utils import compress_text
18 |
19 |
20 | class StringIndex(long):
21 | # Methods below are needed due to http://bugs.jython.org/issue1828
22 |
23 | def __str__(self):
24 | return long.__str__(self).rstrip('L')
25 |
26 | def __nonzero__(self):
27 | return bool(long(self))
28 |
29 |
30 | class StringCache(object):
31 | _compress_threshold = 80
32 | _use_compressed_threshold = 1.1
33 | _zero_index = StringIndex(0)
34 |
35 | def __init__(self):
36 | self._cache = {'*': self._zero_index}
37 |
38 | def add(self, text):
39 | if not text:
40 | return self._zero_index
41 | text = self._encode(text)
42 | if text not in self._cache:
43 | self._cache[text] = StringIndex(len(self._cache))
44 | return self._cache[text]
45 |
46 | def _encode(self, text):
47 | raw = self._raw(text)
48 | if raw in self._cache or len(raw) < self._compress_threshold:
49 | return raw
50 | compressed = compress_text(text)
51 | if len(compressed) * self._use_compressed_threshold < len(raw):
52 | return compressed
53 | return raw
54 |
55 | def _raw(self, text):
56 | return '*'+text
57 |
58 | def dump(self):
59 | return tuple(item[0] for item in sorted(self._cache.iteritems(),
60 | key=itemgetter(1)))
61 |
--------------------------------------------------------------------------------
/lib/robot/result/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | """Implements parsing results from XML output files.
16 |
17 | The entry point of this API is the :func:`~.resultbuilder.ExecutionResult`
18 | factory method, which returns an instance of
19 | :class:`~.executionresult.Result`.
20 |
21 | This package is considered stable.
22 |
23 | Example
24 | -------
25 |
26 | The example below reads a given output file and marks each test case whose
27 | execution time is longer than three minutes failed. The
28 | :class:`~.executionresult.Result` object is then written back to disk and
29 | normal log and report files could be generated with ``rebot`` tool.
30 |
31 | .. literalinclude:: /../../doc/api/code_examples/check_test_times.py
32 | """
33 |
34 | from .resultbuilder import ExecutionResult
35 |
--------------------------------------------------------------------------------
/lib/robot/result/executionerrors.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from robot.model import ItemList
16 | from robot.utils import setter
17 |
18 | from .message import Message
19 |
20 |
21 | class ExecutionErrors(object):
22 | message_class = Message
23 |
24 | def __init__(self, messages=None):
25 | self.messages = messages
26 |
27 | @setter
28 | def messages(self, msgs):
29 | return ItemList(self.message_class, items=msgs)
30 |
31 | def add(self, other):
32 | self.messages.extend(other.messages)
33 |
34 | def visit(self, visitor):
35 | visitor.visit_errors(self)
36 |
37 | def __iter__(self):
38 | return iter(self.messages)
39 |
40 | def __len__(self):
41 | return len(self.messages)
42 |
--------------------------------------------------------------------------------
/lib/robot/result/executionresult.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from __future__ import with_statement
16 |
17 | from robot.model import Statistics
18 | from robot.reporting.outputwriter import OutputWriter
19 |
20 | from .executionerrors import ExecutionErrors
21 | from .configurer import SuiteConfigurer
22 | from .testsuite import TestSuite
23 |
24 |
25 | class Result(object):
26 | """Contains results of test execution.
27 |
28 | :ivar source: Path to the xml file where results are read from.
29 | :ivar suite: Hierarchical :class:`~.testsuite.TestSuite` results.
30 | :ivar errors: Execution :class:`~.executionerrors.ExecutionErrors`.
31 | """
32 |
33 | def __init__(self, source=None, root_suite=None, errors=None):
34 | self.source = source
35 | self.suite = root_suite or TestSuite()
36 | self.errors = errors or ExecutionErrors()
37 | self.generator = None
38 | self._status_rc = True
39 | self._stat_config = {}
40 |
41 | @property
42 | def statistics(self):
43 | """Test execution :class:`~robot.model.statistics.Statistics`."""
44 | return Statistics(self.suite, **self._stat_config)
45 |
46 | @property
47 | def return_code(self):
48 | """Return code (integer) of test execution."""
49 | if self._status_rc:
50 | return min(self.suite.statistics.critical.failed, 250)
51 | return 0
52 |
53 | def configure(self, status_rc=True, suite_config={}, stat_config={}):
54 | SuiteConfigurer(**suite_config).configure(self.suite)
55 | self._status_rc = status_rc
56 | self._stat_config = stat_config
57 |
58 | def visit(self, visitor):
59 | visitor.visit_result(self)
60 |
61 | def save(self, path=None):
62 | self.visit(OutputWriter(path or self.source))
63 |
64 |
65 | class CombinedResult(Result):
66 |
67 | def __init__(self, others):
68 | Result.__init__(self)
69 | for other in others:
70 | self.add_result(other)
71 |
72 | def add_result(self, other):
73 | self.suite.suites.append(other.suite)
74 | self.errors.add(other.errors)
75 |
--------------------------------------------------------------------------------
/lib/robot/result/keyword.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from robot import model, utils
16 |
17 | from .message import Message
18 |
19 |
20 | class Keyword(model.Keyword):
21 | __slots__ = ['status', 'starttime', 'endtime']
22 | message_class = Message
23 |
24 | def __init__(self, name='', doc='', args=None, type='kw', timeout='',
25 | status='FAIL', starttime=None, endtime=None):
26 | """Results of a single keyword.
27 |
28 | :ivar name: Keyword name.
29 | :ivar parent: :class:`~.testsuite.TestSuite` or
30 | :class:`~.testcase.TestCase` that contains this keyword.
31 | :ivar doc: Keyword documentation.
32 | :ivar args: Keyword arguments, a list of strings.
33 | :ivar type: 'SETUP', 'TEARDOWN' or 'KW'.
34 | :ivar timeout: Keyword timeout.
35 | :ivar messages: Log messages, a list of :class:`~.message.Message`
36 | instances.
37 | :ivar keywords: Child keyword results, a list of
38 | :class:`~.Keyword`. instances
39 | :ivar status: String 'PASS' of 'FAIL'.
40 | :ivar starttime: Keyword execution start time as a timestamp.
41 | :ivar endtime: Keyword execution end time as a timestamp.
42 | """
43 | model.Keyword.__init__(self, name, doc, args, type, timeout)
44 | self.status = status
45 | self.starttime = starttime
46 | self.endtime = endtime
47 |
48 | @property
49 | def elapsedtime(self):
50 | return utils.get_elapsed_time(self.starttime, self.endtime)
51 |
52 | @property
53 | def passed(self):
54 | return self.status == 'PASS'
55 |
--------------------------------------------------------------------------------
/lib/robot/result/message.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from robot import model
16 |
17 |
18 | class Message(model.Message):
19 | __slots__ = []
20 |
--------------------------------------------------------------------------------
/lib/robot/result/messagefilter.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from robot.output.loggerhelper import IsLogged
16 |
17 | from robot.model import SuiteVisitor
18 |
19 |
20 | class MessageFilter(SuiteVisitor):
21 |
22 | def __init__(self, loglevel):
23 | self._is_logged = IsLogged(loglevel or 'TRACE')
24 |
25 | def start_keyword(self, keyword):
26 | keyword.messages = [msg for msg in keyword.messages
27 | if self._is_logged(msg.level)]
28 |
--------------------------------------------------------------------------------
/lib/robot/result/suiteteardownfailed.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from robot.model import SuiteVisitor
16 |
17 |
18 | class SuiteTeardownFailureHandler(SuiteVisitor):
19 |
20 | def __init__(self, suite_generator):
21 | self._should_handle = suite_generator == 'ROBOT'
22 |
23 | def start_suite(self, suite):
24 | if not self._should_handle:
25 | return False
26 | if self._suite_teardown_failed(suite.keywords.teardown):
27 | suite.visit(SuiteTeardownFailed())
28 |
29 | def _suite_teardown_failed(self, teardown):
30 | return bool(teardown and not teardown.passed)
31 |
32 | def start_test(self, test):
33 | return False
34 |
35 | def start_keyword(self, keyword):
36 | return False
37 |
38 |
39 | class SuiteTeardownFailed(SuiteVisitor):
40 | _normal_msg = 'Teardown of the parent suite failed.'
41 | _also_msg = '\n\nAlso teardown of the parent suite failed.'
42 |
43 | def __init__(self):
44 | self._top_level_visited = False
45 |
46 | def start_suite(self, suite):
47 | if self._top_level_visited:
48 | self._set_message(suite)
49 | self._top_level_visited = True
50 |
51 | def visit_test(self, test):
52 | test.status = 'FAIL'
53 | self._set_message(test)
54 |
55 | def _set_message(self, item):
56 | item.message += self._also_msg if item.message else self._normal_msg
57 |
58 | def visit_keyword(self, keyword):
59 | pass
60 |
--------------------------------------------------------------------------------
/lib/robot/result/testcase.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from robot import model, utils
16 |
17 | from keyword import Keyword
18 |
19 |
20 | class TestCase(model.TestCase):
21 | __slots__ = ['status', 'message', 'starttime', 'endtime']
22 | keyword_class = Keyword
23 |
24 | def __init__(self, name='', doc='', tags=None, timeout='', status='FAIL',
25 | message='', starttime=None, endtime=None):
26 | """Results of a single test case.
27 |
28 | :ivar name: Test case name.
29 | :ivar parent: :class:`~.testsuite.TestSuite` that contains this test.
30 | :ivar doc: Test case documentation.
31 | :ivar tags: Test case tags, a list of strings.
32 | :ivar timeout: Test case timeout.
33 | :ivar keywords: Keyword results, a list of :class:`~.keyword.Keyword`.
34 | instances and contains also possible setup and teardown keywords.
35 | :ivar status: String 'PASS' of 'FAIL'.
36 | :ivar message: Possible failure message.
37 | :ivar starttime: Test case execution start time as a timestamp.
38 | :ivar endtime: Test case execution end time as a timestamp.
39 | """
40 | model.TestCase.__init__(self, name, doc, tags, timeout)
41 | self.status = status
42 | self.message = message
43 | self.starttime = starttime
44 | self.endtime = endtime
45 |
46 | @property
47 | def elapsedtime(self):
48 | return utils.get_elapsed_time(self.starttime, self.endtime)
49 |
50 | @property
51 | def passed(self):
52 | return self.status == 'PASS'
53 |
--------------------------------------------------------------------------------
/lib/robot/result/testsuite.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from itertools import chain
16 |
17 | from robot.model import TotalStatisticsBuilder
18 | from robot import model, utils
19 |
20 | from messagefilter import MessageFilter
21 | from keywordremover import KeywordRemover
22 | from testcase import TestCase
23 | from keyword import Keyword
24 |
25 |
26 | class TestSuite(model.TestSuite):
27 | __slots__ = ['message', 'starttime', 'endtime']
28 | test_class = TestCase
29 | keyword_class = Keyword
30 |
31 | def __init__(self, source='', name='', doc='', metadata=None,
32 | message='', starttime=None, endtime=None):
33 | """Results of a single test suite.
34 |
35 | :ivar parent: Parent :class:`TestSuite` or `None`.
36 | :ivar source: Path to the source file.
37 | :ivar name: Test suite name.
38 | :ivar doc: Test suite documentation.
39 | :ivar metadata: Test suite metadata as a dictionary.
40 | :ivar suites: Child suite results.
41 | :ivar tests: Test case results. a list of :class:`~.testcase.TestCase`
42 | instances.
43 | :ivar keywords: A list containing setup and teardown results.
44 | :ivar message: Possible failure message.
45 | :ivar starttime: Test suite execution start time as a timestamp.
46 | :ivar endtime: Test suite execution end time as a timestamp.
47 | """
48 | model.TestSuite.__init__(self, source, name, doc, metadata)
49 | self.message = message
50 | self.starttime = starttime
51 | self.endtime = endtime
52 |
53 | @property
54 | def status(self):
55 | return 'FAIL' if self.statistics.critical.failed else 'PASS'
56 |
57 | @property
58 | def statistics(self):
59 | return TotalStatisticsBuilder(self).stats
60 |
61 | @property
62 | def full_message(self):
63 | if not self.message:
64 | return self.statistics.message
65 | return '%s\n\n%s' % (self.message, self.statistics.message)
66 |
67 | @property
68 | def elapsedtime(self):
69 | if self.starttime and self.endtime:
70 | return utils.get_elapsed_time(self.starttime, self.endtime)
71 | return sum(child.elapsedtime for child in
72 | chain(self.suites, self.tests, self.keywords))
73 |
74 | def remove_keywords(self, how):
75 | self.visit(KeywordRemover(how))
76 |
77 | def filter_messages(self, log_level='TRACE'):
78 | self.visit(MessageFilter(log_level))
79 |
--------------------------------------------------------------------------------
/lib/robot/result/visitor.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from robot.model import SuiteVisitor
16 |
17 |
18 | class ResultVisitor(SuiteVisitor):
19 |
20 | def visit_result(self, result):
21 | if self.start_result(result) is not False:
22 | result.suite.visit(self)
23 | result.statistics.visit(self)
24 | result.errors.visit(self)
25 | self.end_result(result)
26 |
27 | def start_result(self, result):
28 | pass
29 |
30 | def end_result(self, result):
31 | pass
32 |
33 | def visit_statistics(self, stats):
34 | if self.start_statistics(stats) is not False:
35 | stats.total.visit(self)
36 | stats.tags.visit(self)
37 | stats.suite.visit(self)
38 | self.end_statistics(stats)
39 |
40 | def start_statistics(self, stats):
41 | pass
42 |
43 | def end_statistics(self, stats):
44 | pass
45 |
46 | def visit_total_statistics(self, stats):
47 | if self.start_total_statistics(stats) is not False:
48 | for stat in stats:
49 | stat.visit(self)
50 | self.end_total_statistics(stats)
51 |
52 | def start_total_statistics(self, stats):
53 | pass
54 |
55 | def end_total_statistics(self, stats):
56 | pass
57 |
58 | def visit_tag_statistics(self, stats):
59 | if self.start_tag_statistics(stats) is not False:
60 | for stat in stats:
61 | stat.visit(self)
62 | self.end_tag_statistics(stats)
63 |
64 | def start_tag_statistics(self, stats):
65 | pass
66 |
67 | def end_tag_statistics(self, stats):
68 | pass
69 |
70 | def visit_suite_statistics(self, stats):
71 | if self.start_suite_statistics(stats) is not False:
72 | for stat in stats:
73 | stat.visit(self)
74 | self.end_suite_statistics(stats)
75 |
76 | def start_suite_statistics(self, stats):
77 | pass
78 |
79 | def end_suite_statistics(self, suite_stats):
80 | pass
81 |
82 | def visit_stat(self, stat):
83 | if self.start_stat(stat) is not False:
84 | self.end_stat(stat)
85 |
86 | def start_stat(self, stat):
87 | pass
88 |
89 | def end_stat(self, stat):
90 | pass
91 |
92 | def visit_errors(self, errors):
93 | self.start_errors(errors)
94 | for msg in errors:
95 | msg.visit(self)
96 | self.end_errors(errors)
97 |
98 | def start_errors(self, errors):
99 | pass
100 |
101 | def end_errors(self, errors):
102 | pass
103 |
--------------------------------------------------------------------------------
/lib/robot/runner.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | import sys
18 |
19 | # Allows running as a script. __name__ check needed with multiprocessing:
20 | # http://code.google.com/p/robotframework/issues/detail?id=1137
21 | if 'robot' not in sys.modules and __name__ == '__main__':
22 | import pythonpathsetter
23 |
24 | from robot import run_cli
25 | from robot.output import LOGGER
26 |
27 | LOGGER.warn("'robot/runner.py' entry point is deprecated and will be removed "
28 | "in Robot Framework 2.8. Use new 'robot/run.py' instead.")
29 |
30 | if __name__ == '__main__':
31 | run_cli(sys.argv[1:])
32 |
--------------------------------------------------------------------------------
/lib/robot/running/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | """Implements the core test execution logic.
16 |
17 | The code in this package is in many places suboptimal and likely to change in
18 | RF 2.8. External code should use this package with care.
19 |
20 | Currently, the main entry point is the :func:`~.model.TestSuite` factory
21 | method.
22 | """
23 |
24 | from .model import TestSuite
25 | from .keywords import Keyword
26 | from .testlibraries import TestLibrary
27 | from .runkwregister import RUN_KW_REGISTER
28 | from .signalhandler import STOP_SIGNAL_MONITOR
29 | from .context import EXECUTION_CONTEXTS
30 |
31 |
32 | def UserLibrary(path):
33 | """Create a user library instance from given resource file.
34 |
35 | This is used at least by libdoc.py."""
36 | from robot.parsing import ResourceFile
37 | from robot import utils
38 | from .arguments import UserKeywordArguments
39 | from .userkeyword import UserLibrary as RuntimeUserLibrary
40 |
41 | resource = ResourceFile(path).populate()
42 | ret = RuntimeUserLibrary(resource.keyword_table.keywords, path)
43 | for handler in ret.handlers.values(): # This is done normally only at runtime.
44 | handler.arguments = UserKeywordArguments(handler._keyword_args,
45 | handler.longname)
46 | handler.doc = utils.unescape(handler._doc)
47 | ret.doc = resource.setting_table.doc.value
48 | return ret
49 |
50 |
--------------------------------------------------------------------------------
/lib/robot/running/defaultvalues.py:
--------------------------------------------------------------------------------
1 |
2 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 |
16 | from fixture import Setup, Teardown
17 | from timeouts import TestTimeout
18 |
19 |
20 | class DefaultValues(object):
21 |
22 | def __init__(self, settings, parent_default_values=None):
23 | self._parent = parent_default_values
24 | self._setup = settings.test_setup
25 | self._teardown = settings.test_teardown
26 | self._timeout = settings.test_timeout
27 | self._force_tags = settings.force_tags
28 | self._default_tags = settings.default_tags
29 | self._test_template = settings.test_template
30 |
31 | def get_setup(self, tc_setup):
32 | setup = tc_setup if tc_setup.is_set() else self._get_default_setup()
33 | return Setup(setup.name, setup.args)
34 |
35 | def _get_default_setup(self):
36 | if self._setup.is_set() or not self._parent:
37 | return self._setup
38 | return self._parent._get_default_setup()
39 |
40 | def get_teardown(self, tc_teardown):
41 | td = tc_teardown if tc_teardown.is_set() else self._get_default_teardown()
42 | return Teardown(td.name, td.args)
43 |
44 | def _get_default_teardown(self):
45 | if self._teardown.is_set() or not self._parent:
46 | return self._teardown
47 | return self._parent._get_default_teardown()
48 |
49 | def get_timeout(self, tc_timeout):
50 | timeout = tc_timeout if tc_timeout.is_set() else self._get_default_timeout()
51 | return TestTimeout(timeout.value, timeout.message)
52 |
53 | def _get_default_timeout(self):
54 | if self._timeout.is_set() or not self._parent:
55 | return self._timeout
56 | return self._parent._get_default_timeout()
57 |
58 | def get_tags(self, tc_tags):
59 | tags = tc_tags if tc_tags.is_set() else self._default_tags
60 | return (tags + self._get_force_tags()).value
61 |
62 | def _get_force_tags(self):
63 | if not self._parent:
64 | return self._force_tags
65 | return self._force_tags + self._parent._get_force_tags()
66 |
67 | def get_template(self, template):
68 | tmpl = (template if template.is_set() else self._test_template).value
69 | return tmpl if tmpl and tmpl.upper() != 'NONE' else None
70 |
--------------------------------------------------------------------------------
/lib/robot/running/fixture.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from robot.errors import ExecutionFailed, DataError
16 |
17 | from .keywords import Keyword
18 |
19 |
20 | class _Fixture(object):
21 |
22 | def __init__(self, name, args):
23 | self.name = name or ''
24 | self.args = args
25 | self._keyword = None
26 |
27 | def replace_variables(self, variables, errors):
28 | if self.name:
29 | try:
30 | self.name = variables.replace_string(self.name)
31 | except DataError, err:
32 | errors.append('Replacing variables from %s failed: %s'
33 | % (self.__class__.__name__, unicode(err)))
34 | if self.name.upper() != 'NONE':
35 | self._keyword = Keyword(self.name, self.args,
36 | type=type(self).__name__.lower())
37 |
38 | def run(self, context, error_listener):
39 | if self._keyword:
40 | try:
41 | self._keyword.run(context)
42 | except ExecutionFailed, err:
43 | error_listener.notify(err)
44 |
45 | def serialize(self, serializer):
46 | if self._keyword:
47 | serializer.start_keyword(self._keyword)
48 | serializer.end_keyword(self._keyword)
49 |
50 |
51 | class Setup(_Fixture): pass
52 | class Teardown(_Fixture): pass
53 |
54 |
55 |
56 | class SuiteTearDownListener(object):
57 | def __init__(self, suite):
58 | self._suite = suite
59 | def notify(self, error):
60 | self._suite.suite_teardown_failed('Suite teardown failed:\n%s'
61 | % unicode(error))
62 |
63 |
64 | class SuiteSetupListener(object):
65 | def __init__(self, suite):
66 | self._suite = suite
67 | def notify(self, error):
68 | self._suite.run_errors.suite_setup_err(error)
69 |
70 |
71 | class _TestListener(object):
72 | def __init__(self, test):
73 | self._test = test
74 | def notify(self, error):
75 | self._test.keyword_failed(error)
76 | self._notify_run_errors(error)
77 |
78 |
79 | class TestSetupListener(_TestListener):
80 | def _notify_run_errors(self, error):
81 | self._test.run_errors.setup_err(unicode(error))
82 |
83 |
84 | class TestTeardownListener(_TestListener):
85 | def _notify_run_errors(self, error):
86 | self._test.run_errors.teardown_err(unicode(error))
87 |
88 |
89 | class KeywordTeardownListener(object):
90 | def __init__(self, run_errors):
91 | self._run_errors = run_errors
92 | def notify(self, error):
93 | self._run_errors.teardown_err(error)
94 |
--------------------------------------------------------------------------------
/lib/robot/running/runkwregister.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 |
16 | import inspect
17 |
18 | from robot import utils
19 |
20 |
21 | class _RunKeywordRegister:
22 |
23 | def __init__(self):
24 | self._libs = {}
25 |
26 | def register_run_keyword(self, libname, keyword, args_to_process=None):
27 | if args_to_process is None:
28 | args_to_process = self._get_args_from_method(keyword)
29 | keyword = keyword.__name__
30 | if libname not in self._libs:
31 | self._libs[libname] = utils.NormalizedDict(ignore=['_'])
32 | self._libs[libname][keyword] = int(args_to_process)
33 |
34 | def get_args_to_process(self, libname, kwname):
35 | if libname in self._libs and kwname in self._libs[libname]:
36 | return self._libs[libname][kwname]
37 | return -1
38 |
39 | def is_run_keyword(self, libname, kwname):
40 | return self.get_args_to_process(libname, kwname) >= 0
41 |
42 | def _get_args_from_method(self, method):
43 | if inspect.ismethod(method):
44 | return method.im_func.func_code.co_argcount -1
45 | elif inspect.isfunction(method):
46 | return method.func_code.co_argcount
47 | raise ValueError("Needs function or method!")
48 |
49 |
50 | RUN_KW_REGISTER = _RunKeywordRegister()
51 |
--------------------------------------------------------------------------------
/lib/robot/running/signalhandler.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import sys
16 | from threading import currentThread
17 | try:
18 | import signal
19 | except ImportError:
20 | signal = None # IronPython 2.6 doesn't have signal module by default
21 | if sys.platform.startswith('java'):
22 | from java.lang import IllegalArgumentException
23 | else:
24 | IllegalArgumentException = None
25 |
26 | from robot.errors import ExecutionFailed
27 | from robot.output import LOGGER
28 |
29 |
30 | class _StopSignalMonitor(object):
31 |
32 | def __init__(self):
33 | self._signal_count = 0
34 | self._running_keyword = False
35 |
36 | def __call__(self, signum, frame):
37 | self._signal_count += 1
38 | LOGGER.info('Received signal: %s.' % signum)
39 | if self._signal_count > 1:
40 | sys.__stderr__.write('Execution forcefully stopped.\n')
41 | raise SystemExit()
42 | sys.__stderr__.write('Second signal will force exit.\n')
43 | if self._running_keyword and not sys.platform.startswith('java'):
44 | self._stop_execution_gracefully()
45 |
46 | def _stop_execution_gracefully(self):
47 | raise ExecutionFailed('Execution terminated by signal', exit=True)
48 |
49 | def start(self):
50 | if signal:
51 | for signum in signal.SIGINT, signal.SIGTERM:
52 | self._register_signal_handler(signum)
53 |
54 | def _register_signal_handler(self, signum):
55 | try:
56 | signal.signal(signum, self)
57 | except (ValueError, IllegalArgumentException), err:
58 | # ValueError occurs e.g. if Robot doesn't run on main thread.
59 | # IllegalArgumentException is http://bugs.jython.org/issue1729
60 | if currentThread().getName() == 'MainThread':
61 | self._warn_about_registeration_error(signum, err)
62 |
63 | def _warn_about_registeration_error(self, signum, err):
64 | name, ctrlc = {signal.SIGINT: ('INT', 'or with Ctrl-C '),
65 | signal.SIGTERM: ('TERM', '')}[signum]
66 | LOGGER.warn('Registering signal %s failed. Stopping execution '
67 | 'gracefully with this signal %sis not possible. '
68 | 'Original error was: %s' % (name, ctrlc, err))
69 |
70 | def start_running_keyword(self, in_teardown):
71 | self._running_keyword = True
72 | if self._signal_count and not in_teardown:
73 | self._stop_execution_gracefully()
74 |
75 | def stop_running_keyword(self):
76 | self._running_keyword = False
77 |
78 |
79 | STOP_SIGNAL_MONITOR = _StopSignalMonitor()
80 |
--------------------------------------------------------------------------------
/lib/robot/running/timeouts/stoppablethread.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import sys
16 | import threading
17 |
18 |
19 | class Thread(threading.Thread):
20 | """A subclass of threading.Thread, with a stop() method.
21 |
22 | Original version posted by Connelly Barnes to python-list and available at
23 | http://mail.python.org/pipermail/python-list/2004-May/219465.html
24 |
25 | This version mainly has kill() changed to stop() to match java.lang.Thread.
26 |
27 | This is a hack but seems to be the best way the get this done. Only used
28 | in Python because in Jython we can use java.lang.Thread.
29 | """
30 |
31 | def __init__(self, runner, name=None):
32 | threading.Thread.__init__(self, target=runner, name=name)
33 | self._stopped = False
34 |
35 | def start(self):
36 | self.__run_backup = self.run
37 | self.run = self.__run
38 | threading.Thread.start(self)
39 |
40 | def stop(self):
41 | self._stopped = True
42 |
43 | def __run(self):
44 | """Hacked run function, which installs the trace."""
45 | sys.settrace(self._globaltrace)
46 | self.__run_backup()
47 | self.run = self.__run_backup
48 |
49 | def _globaltrace(self, frame, why, arg):
50 | if why == 'call':
51 | return self._localtrace
52 | else:
53 | return None
54 |
55 | def _localtrace(self, frame, why, arg):
56 | if self._stopped:
57 | if why == 'line':
58 | raise SystemExit()
59 | return self._localtrace
60 |
--------------------------------------------------------------------------------
/lib/robot/running/timeouts/timeoutsignaling.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from signal import setitimer, signal, SIGALRM, ITIMER_REAL
16 |
17 | from robot.errors import TimeoutError
18 |
19 |
20 | class Timeout(object):
21 |
22 | def __init__(self, timeout, error):
23 | self._timeout = timeout
24 | self._error = error
25 |
26 | def execute(self, runnable):
27 | self._start_timer()
28 | try:
29 | return runnable()
30 | finally:
31 | self._stop_timer()
32 |
33 | def _start_timer(self):
34 | signal(SIGALRM, self._raise_timeout_error)
35 | setitimer(ITIMER_REAL, self._timeout)
36 |
37 | def _raise_timeout_error(self, signum, frame):
38 | raise TimeoutError(self._error)
39 |
40 | def _stop_timer(self):
41 | setitimer(ITIMER_REAL, 0)
42 |
--------------------------------------------------------------------------------
/lib/robot/running/timeouts/timeoutthread.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import sys
16 | from threading import Event
17 |
18 | from robot.errors import TimeoutError
19 |
20 | if sys.platform.startswith('java'):
21 | from java.lang import Thread, Runnable
22 | else:
23 | from .stoppablethread import Thread
24 | Runnable = object
25 |
26 |
27 | TIMEOUT_THREAD_NAME = 'RobotFrameworkTimeoutThread'
28 |
29 |
30 | class ThreadedRunner(Runnable):
31 |
32 | def __init__(self, runnable):
33 | self._runnable = runnable
34 | self._notifier = Event()
35 | self._result = None
36 | self._error = None
37 | self._traceback = None
38 | self._thread = None
39 |
40 | def run(self):
41 | try:
42 | self._result = self._runnable()
43 | except:
44 | self._error, self._traceback = sys.exc_info()[1:]
45 | self._notifier.set()
46 |
47 | __call__ = run
48 |
49 | def run_in_thread(self, timeout):
50 | self._thread = Thread(self, name=TIMEOUT_THREAD_NAME)
51 | self._thread.setDaemon(True)
52 | self._thread.start()
53 | self._notifier.wait(timeout)
54 | return self._notifier.isSet()
55 |
56 | def get_result(self):
57 | if self._error:
58 | raise self._error, None, self._traceback
59 | return self._result
60 |
61 | def stop_thread(self):
62 | self._thread.stop()
63 |
64 |
65 | class Timeout(object):
66 |
67 | def __init__(self, timeout, error):
68 | self._timeout = timeout
69 | self._error = error
70 |
71 | def execute(self, runnable):
72 | runner = ThreadedRunner(runnable)
73 | if runner.run_in_thread(self._timeout):
74 | return runner.get_result()
75 | runner.stop_thread()
76 | raise TimeoutError(self._error)
77 |
78 |
79 |
--------------------------------------------------------------------------------
/lib/robot/running/timeouts/timeoutwin.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import ctypes
16 | import thread
17 | import time
18 | from threading import Timer
19 |
20 | from robot.errors import TimeoutError
21 |
22 |
23 | class Timeout(object):
24 |
25 | def __init__(self, timeout, timeout_error):
26 | self._runner_thread_id = thread.get_ident()
27 | self._timeout_error = self._create_timeout_error_class(timeout_error)
28 | self._timer = Timer(timeout, self._raise_timeout_error)
29 | self._timeout_occurred = False
30 |
31 | def _create_timeout_error_class(self, timeout_error):
32 | return type(TimeoutError.__name__,
33 | (TimeoutError,),
34 | {'__unicode__': lambda self: timeout_error})
35 |
36 | def execute(self, runnable):
37 | self._start_timer()
38 | try:
39 | return runnable()
40 | finally:
41 | self._stop_timer()
42 |
43 | def _start_timer(self):
44 | self._timer.start()
45 |
46 | def _stop_timer(self):
47 | self._timer.cancel()
48 | # In case timeout has occurred but the exception has not yet been
49 | # thrown we need to do this to ensure that the exception
50 | # is not thrown in an unsafe location
51 | if self._timeout_occurred:
52 | self._cancel_exception()
53 | raise self._timeout_error()
54 |
55 | def _raise_timeout_error(self):
56 | self._timeout_occurred = True
57 | return_code = self._try_to_raise_timeout_error_in_runner_thread()
58 | # return code tells how many threads have been influenced
59 | while return_code > 1: # if more than one then cancel and retry
60 | self._cancel_exception()
61 | time.sleep(0) # yield so that other threads will get time
62 | return_code = self._try_to_raise_timeout_error_in_runner_thread()
63 |
64 | def _try_to_raise_timeout_error_in_runner_thread(self):
65 | return ctypes.pythonapi.PyThreadState_SetAsyncExc(
66 | self._runner_thread_id,
67 | ctypes.py_object(self._timeout_error))
68 |
69 | def _cancel_exception(self):
70 | ctypes.pythonapi.PyThreadState_SetAsyncExc(self._runner_thread_id, None)
71 |
--------------------------------------------------------------------------------
/lib/robot/utils/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | """Various generic utility classes and functions.
16 |
17 | Provided utilities are generally stable, but absolute backwards compatibility
18 | between major versions is not guaranteed.
19 | """
20 |
21 | from .argumentparser import ArgumentParser
22 | from .application import Application
23 | from .compress import compress_text
24 | from .connectioncache import ConnectionCache
25 | from .encoding import (decode_output, encode_output,
26 | decode_from_system, encode_to_system, utf8open)
27 | from .error import (get_error_message, get_error_details, ErrorDetails,
28 | RERAISED_EXCEPTIONS)
29 | from .escaping import escape, unescape
30 | from .etreewrapper import ET, ETSource
31 | from .markuputils import html_format, html_escape, xml_escape, attribute_escape
32 | from .markupwriters import HtmlWriter, XmlWriter, NullMarkupWriter
33 | from .importer import Importer
34 | from .match import eq, matches, matches_any, Matcher, MultiMatcher
35 | from .misc import plural_or_not, printable_name, seq2str, seq2str2, getdoc, isatty
36 | from .normalizing import lower, normalize, normalize_tags, NormalizedDict
37 | from .robotenv import get_env_var, set_env_var, del_env_var, get_env_vars
38 | from .robotpath import normpath, abspath, get_link_path
39 | from .robottime import (get_timestamp, get_start_timestamp, format_time,
40 | get_time, get_elapsed_time, elapsed_time_to_string,
41 | timestr_to_secs, secs_to_timestr, secs_to_timestamp,
42 | timestamp_to_secs, parse_time)
43 | from .setter import setter
44 | from .text import (cut_long_message, format_assign_message,
45 | pad_console_length, get_console_length)
46 | from .unic import unic, safe_repr
47 |
48 | # TODO: for backwards compatibility, remove in RF 2.8
49 | html_attr_escape = attribute_escape
50 |
51 | import sys
52 | is_jython = sys.platform.startswith('java')
53 | del sys
54 |
--------------------------------------------------------------------------------
/lib/robot/utils/compress.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import base64
16 | import sys
17 |
18 |
19 | def compress_text(text):
20 | return base64.b64encode(_compress(text.encode('UTF-8')))
21 |
22 |
23 | if not sys.platform.startswith('java'):
24 |
25 | import zlib
26 |
27 | def _compress(text):
28 | return zlib.compress(text, 9)
29 |
30 | else:
31 | # Custom compress implementation needed to avoid memory leak:
32 | # http://bugs.jython.org/issue1775
33 | # This is based on the zlib.compress in Jython 2.5.2 but has a memory
34 | # leak fix and is also a little faster.
35 |
36 | from java.util.zip import Deflater
37 | import jarray
38 |
39 | _DEFLATOR = Deflater(9, False)
40 |
41 | def _compress(text):
42 | _DEFLATOR.setInput(text)
43 | _DEFLATOR.finish()
44 | buf = jarray.zeros(1024, 'b')
45 | compressed = []
46 | while not _DEFLATOR.finished():
47 | length = _DEFLATOR.deflate(buf, 0, 1024)
48 | compressed.append(buf[:length].tostring())
49 | _DEFLATOR.reset()
50 | return ''.join(compressed)
51 |
--------------------------------------------------------------------------------
/lib/robot/utils/encoding.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import sys
16 | import codecs
17 | from contextlib import contextmanager
18 |
19 | from .encodingsniffer import get_output_encoding, get_system_encoding
20 | from .unic import unic
21 |
22 |
23 | OUTPUT_ENCODING = get_output_encoding()
24 | SYSTEM_ENCODING = get_system_encoding()
25 |
26 |
27 | def decode_output(string):
28 | """Decodes bytes from console encoding to Unicode."""
29 | return unic(string, OUTPUT_ENCODING)
30 |
31 | def encode_output(string, errors='replace'):
32 | """Encodes Unicode to bytes in console encoding."""
33 | # http://ironpython.codeplex.com/workitem/29487
34 | if sys.platform == 'cli':
35 | return string
36 | return string.encode(OUTPUT_ENCODING, errors)
37 |
38 | def decode_from_system(string, can_be_from_java=True):
39 | """Decodes bytes from system (e.g. cli args or env vars) to Unicode."""
40 | if sys.platform == 'cli':
41 | return string
42 | if sys.platform.startswith('java') and can_be_from_java:
43 | # http://bugs.jython.org/issue1592
44 | from java.lang import String
45 | string = String(string)
46 | return unic(string, SYSTEM_ENCODING)
47 |
48 | def encode_to_system(string, errors='replace'):
49 | """Encodes Unicode to system encoding (e.g. cli args and env vars)."""
50 | return string.encode(SYSTEM_ENCODING, errors)
51 |
52 | # workaround for Python 2.5.0 bug: http://bugs.python.org/issue1586513
53 | @contextmanager
54 | def utf8open(filename, mode='r'):
55 | file = codecs.open(filename, mode=mode, encoding='utf8')
56 | try:
57 | yield file
58 | finally:
59 | file.close()
60 |
--------------------------------------------------------------------------------
/lib/robot/utils/encodingsniffer.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import sys
16 | import os
17 |
18 |
19 | UNIXY = os.sep == '/'
20 | JYTHON = sys.platform.startswith('java')
21 | if UNIXY:
22 | DEFAULT_SYSTEM_ENCODING = 'UTF-8'
23 | DEFAULT_OUTPUT_ENCODING = 'UTF-8'
24 | else:
25 | DEFAULT_SYSTEM_ENCODING = 'cp1252'
26 | DEFAULT_OUTPUT_ENCODING = 'cp437'
27 |
28 |
29 | def get_system_encoding():
30 | encoding = _get_python_file_system_encoding()
31 | if not encoding and JYTHON:
32 | encoding = _get_java_file_system_encoding()
33 | return encoding or DEFAULT_SYSTEM_ENCODING
34 |
35 | def _get_python_file_system_encoding():
36 | encoding = sys.getfilesystemencoding()
37 | return encoding if _is_valid(encoding) else None
38 |
39 | def _get_java_file_system_encoding():
40 | from java.lang import System
41 | encoding = System.getProperty('file.encoding')
42 | return encoding if _is_valid(encoding) else None
43 |
44 | def _is_valid(encoding):
45 | if not encoding:
46 | return False
47 | try:
48 | 'test'.encode(encoding)
49 | except LookupError:
50 | return False
51 | else:
52 | return True
53 |
54 |
55 | def get_output_encoding():
56 | if _on_buggy_jython():
57 | return DEFAULT_OUTPUT_ENCODING
58 | encoding = _get_encoding_from_standard_streams()
59 | if not encoding and UNIXY:
60 | encoding = _get_encoding_from_unix_environment_variables()
61 | return encoding or DEFAULT_OUTPUT_ENCODING
62 |
63 | def _on_buggy_jython():
64 | # http://bugs.jython.org/issue1568
65 | if UNIXY or not JYTHON:
66 | return False
67 | return sys.platform.startswith('java1.5') or sys.version_info < (2, 5, 2)
68 |
69 | def _get_encoding_from_standard_streams():
70 | # Stream may not have encoding attribute if it is intercepted outside RF
71 | # in Python. Encoding is None if process's outputs are redirected.
72 | for stream in sys.__stdout__, sys.__stderr__, sys.__stdin__:
73 | encoding = getattr(stream, 'encoding', None)
74 | if _is_valid(encoding):
75 | return encoding
76 | return None
77 |
78 | def _get_encoding_from_unix_environment_variables():
79 | for name in 'LANG', 'LC_CTYPE', 'LANGUAGE', 'LC_ALL':
80 | if name in os.environ:
81 | # Encoding can be in format like `UTF-8` or `en_US.UTF-8`
82 | encoding = os.environ[name].split('.')[-1]
83 | if _is_valid(encoding):
84 | return encoding
85 | return None
86 |
--------------------------------------------------------------------------------
/lib/robot/utils/escaping.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import re
16 |
17 |
18 | _ESCAPE_RE = re.compile(r'(\\+)([^\\]{0,2})') # escapes and nextchars
19 | _SEQS_TO_BE_ESCAPED = ('\\', '${', '@{', '%{', '&{', '*{' , '=')
20 |
21 |
22 | def escape(item):
23 | if not isinstance(item, basestring):
24 | return item
25 | for seq in _SEQS_TO_BE_ESCAPED:
26 | if seq in item:
27 | item = item.replace(seq, '\\' + seq)
28 | return item
29 |
30 |
31 | def unescape(item):
32 | if not isinstance(item, basestring):
33 | return item
34 | result = []
35 | unprocessed = item
36 | while True:
37 | res = _ESCAPE_RE.search(unprocessed)
38 | # If no escapes found append string to result and exit loop
39 | if res is None:
40 | result.append(unprocessed)
41 | break
42 | # Split string to pre match, escapes, nextchars and unprocessed parts
43 | # (e.g. '') where nextchars contains 0-2 chars
44 | # and unprocessed may contain more escapes. Pre match part contains
45 | # no escapes can is appended directly to result.
46 | result.append(unprocessed[:res.start()])
47 | escapes = res.group(1)
48 | nextchars = res.group(2)
49 | unprocessed = unprocessed[res.end():]
50 | # Append every second escape char to result
51 | result.append('\\' * (len(escapes) / 2))
52 | # Handle '\n', '\r' and '\t'. Note that both '\n' and '\n ' are
53 | # converted to '\n'
54 | if len(escapes) % 2 == 0 or len(nextchars) == 0 \
55 | or nextchars[0] not in ['n','r','t']:
56 | result.append(nextchars)
57 | elif nextchars[0] == 'n':
58 | if len(nextchars) == 1 or nextchars[1] == ' ':
59 | result.append('\n')
60 | else:
61 | result.append('\n' + nextchars[1])
62 | elif nextchars[0] == 'r':
63 | result.append('\r' + nextchars[1:])
64 | else:
65 | result.append('\t' + nextchars[1:])
66 | return ''.join(result)
67 |
--------------------------------------------------------------------------------
/lib/robot/utils/markuputils.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import re
16 |
17 | from .htmlformatters import LinkFormatter, HtmlFormatter
18 |
19 |
20 | _format_url = LinkFormatter().format_url
21 | _generic_escapes = (('&', '&'), ('<', '<'), ('>', '>'))
22 | _attribute_escapes = _generic_escapes \
23 | + (('"', '"'), ('\n', '
'), ('\r', '
'), ('\t', ' '))
24 | _illegal_chars_in_xml = re.compile(u'[\x00-\x08\x0B\x0C\x0E-\x1F\uFFFE\uFFFF]')
25 |
26 |
27 | def html_escape(text):
28 | return _format_url(_escape(text))
29 |
30 |
31 | def xml_escape(text):
32 | return _illegal_chars_in_xml.sub('', _escape(text))
33 |
34 |
35 | def html_format(text):
36 | return HtmlFormatter().format(_escape(text))
37 |
38 |
39 | def attribute_escape(attr):
40 | return _escape(attr, _attribute_escapes)
41 |
42 |
43 | def _escape(text, escapes=_generic_escapes):
44 | for name, value in escapes:
45 | if name in text: # performance optimization
46 | text = text.replace(name, value)
47 | return _illegal_chars_in_xml.sub('', text)
48 |
--------------------------------------------------------------------------------
/lib/robot/utils/match.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import re
16 | from functools import partial
17 |
18 | from .normalizing import normalize
19 |
20 |
21 | def eq(str1, str2, ignore=(), caseless=True, spaceless=True):
22 | str1 = normalize(str1, ignore, caseless, spaceless)
23 | str2 = normalize(str2, ignore, caseless, spaceless)
24 | return str1 == str2
25 |
26 |
27 | # TODO: Remove matches and matches_any in 2.8.
28 | # They aren't used much in 2.7 anymore but don't want to remove them after RC.
29 |
30 | def matches(string, pattern, ignore=(), caseless=True, spaceless=True):
31 | """Deprecated!! Use Matcher instead."""
32 | return Matcher(pattern, ignore, caseless, spaceless).match(string)
33 |
34 |
35 | def matches_any(string, patterns, ignore=(), caseless=True, spaceless=True):
36 | """Deprecated!! Use MultiMatcher instead."""
37 | matcher = MultiMatcher(patterns, ignore, caseless, spaceless)
38 | return matcher.match(string)
39 |
40 |
41 | class Matcher(object):
42 | _pattern_tokenizer = re.compile('(\*|\?)')
43 | _wildcards = {'*': '.*', '?': '.'}
44 |
45 | def __init__(self, pattern, ignore=(), caseless=True, spaceless=True):
46 | self.pattern = pattern
47 | self._normalize = partial(normalize, ignore=ignore, caseless=caseless,
48 | spaceless=spaceless)
49 | self._regexp = self._get_and_compile_regexp(self._normalize(pattern))
50 |
51 | def _get_and_compile_regexp(self, pattern):
52 | pattern = '^%s$' % ''.join(self._yield_regexp(pattern))
53 | return re.compile(pattern, re.DOTALL)
54 |
55 | def _yield_regexp(self, pattern):
56 | for token in self._pattern_tokenizer.split(pattern):
57 | if token in self._wildcards:
58 | yield self._wildcards[token]
59 | else:
60 | yield re.escape(token)
61 |
62 | def match(self, string):
63 | return self._regexp.match(self._normalize(string)) is not None
64 |
65 |
66 | class MultiMatcher(object):
67 |
68 | def __init__(self, patterns=None, ignore=(), caseless=True, spaceless=True,
69 | match_if_no_patterns=False):
70 | self._matchers = [Matcher(pattern, ignore, caseless, spaceless)
71 | for pattern in self._ensure_list(patterns)]
72 | self._match_if_no_patterns = match_if_no_patterns
73 |
74 | def _ensure_list(self, patterns):
75 | if patterns is None:
76 | return []
77 | if isinstance(patterns, basestring):
78 | return [patterns]
79 | return patterns
80 |
81 | def match(self, string):
82 | if self._matchers:
83 | return any(m.match(string) for m in self._matchers)
84 | return self._match_if_no_patterns
85 |
86 | def __len__(self):
87 | return len(self._matchers)
88 |
89 | def __iter__(self):
90 | for matcher in self._matchers:
91 | yield matcher.pattern
92 |
--------------------------------------------------------------------------------
/lib/robot/utils/robotenv.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import os
16 | import sys
17 |
18 | from .encoding import decode_from_system, encode_to_system
19 | from .unic import unic
20 |
21 |
22 | def get_env_var(name, default=None):
23 | value = _get_env_var_from_java(name)
24 | if value is not None:
25 | return value
26 | try:
27 | value = os.environ[_encode(name)]
28 | except KeyError:
29 | return default
30 | else:
31 | return _decode(value)
32 |
33 | def set_env_var(name, value):
34 | os.environ[_encode(name)] = _encode(value)
35 |
36 | def del_env_var(name):
37 | # cannot use os.environ.pop() due to http://bugs.python.org/issue1287
38 | value = get_env_var(name)
39 | if value is not None:
40 | del os.environ[_encode(name)]
41 | return value
42 |
43 | def get_env_vars():
44 | # name is upper-cases consistently on Windows regardless interpreter
45 | return dict((name if os.sep == '/' else name.upper(), get_env_var(name))
46 | for name in (_decode(name) for name in os.environ))
47 |
48 |
49 | def _encode(var):
50 | if isinstance(var, str):
51 | return var
52 | if isinstance(var, unicode):
53 | return encode_to_system(var)
54 | return str(var)
55 |
56 | def _decode(var):
57 | return decode_from_system(var, can_be_from_java=False)
58 |
59 | # Jython hack below needed due to http://bugs.jython.org/issue1841
60 | if not sys.platform.startswith('java'):
61 | def _get_env_var_from_java(name):
62 | return None
63 |
64 | else:
65 | from java.lang import String, System
66 |
67 | def _get_env_var_from_java(name):
68 | name = name if isinstance(name, basestring) else unic(name)
69 | value_set_before_execution = System.getenv(name)
70 | if value_set_before_execution is None:
71 | return None
72 | current_value = String(os.environ[name]).toString()
73 | if value_set_before_execution != current_value:
74 | return None
75 | return value_set_before_execution
76 |
--------------------------------------------------------------------------------
/lib/robot/utils/setter.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 |
16 | class setter(object):
17 |
18 | def __init__(self, method):
19 | self.method = method
20 | self.attr_name = '_setter__' + method.__name__
21 |
22 | def __get__(self, instance, owner):
23 | if instance is None:
24 | return self
25 | try:
26 | return getattr(instance, self.attr_name)
27 | except AttributeError:
28 | raise AttributeError(self.method.__name__)
29 |
30 | def __set__(self, instance, value):
31 | if instance is None:
32 | return
33 | setattr(instance, self.attr_name, self.method(instance, value))
34 |
35 |
36 | class SetterAwareType(type):
37 |
38 | def __new__(cls, name, bases, dct):
39 | slots = dct.get('__slots__')
40 | if slots is not None:
41 | for item in dct.values():
42 | if isinstance(item, setter):
43 | slots.append(item.attr_name)
44 | return type.__new__(cls, name, bases, dct)
45 |
--------------------------------------------------------------------------------
/lib/robot/utils/text.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from unic import unic
16 | from misc import seq2str2
17 | from charwidth import get_char_width
18 |
19 |
20 | _MAX_ASSIGN_LENGTH = 200
21 | _MAX_ERROR_LINES = 40
22 | _MAX_ERROR_LINE_LENGTH = 78
23 | _ERROR_CUT_EXPLN = (' [ Message content over the limit has been removed. ]')
24 |
25 |
26 | def cut_long_message(msg):
27 | lines = msg.splitlines()
28 | lengths = _count_line_lenghts(lines)
29 | if sum(lengths) <= _MAX_ERROR_LINES:
30 | return msg
31 | start = _prune_excess_lines(lines, lengths)
32 | end = _prune_excess_lines(lines, lengths, True)
33 | return '\n'.join(start + [_ERROR_CUT_EXPLN] + end)
34 |
35 | def _prune_excess_lines(lines, lengths, from_end=False):
36 | if from_end:
37 | lines.reverse()
38 | lengths.reverse()
39 | ret = []
40 | total = 0
41 | limit = _MAX_ERROR_LINES/2
42 | for line, length in zip(lines[:limit], lengths[:limit]):
43 | if total + length >= limit:
44 | ret.append(_cut_long_line(line, total, from_end))
45 | break
46 | total += length
47 | ret.append(line)
48 | if from_end:
49 | ret.reverse()
50 | return ret
51 |
52 | def _cut_long_line(line, used, from_end):
53 | available_lines = _MAX_ERROR_LINES/2 - used
54 | available_chars = available_lines * _MAX_ERROR_LINE_LENGTH - 3
55 | if len(line) > available_chars:
56 | if not from_end:
57 | line = line[:available_chars] + '...'
58 | else:
59 | line = '...' + line[-available_chars:]
60 | return line
61 |
62 | def _count_line_lenghts(lines):
63 | return [ _count_virtual_line_length(line) for line in lines ]
64 |
65 | def _count_virtual_line_length(line):
66 | length = len(line) / _MAX_ERROR_LINE_LENGTH
67 | if not len(line) % _MAX_ERROR_LINE_LENGTH == 0 or len(line) == 0:
68 | length += 1
69 | return length
70 |
71 |
72 | def format_assign_message(variable, value, cut_long=True):
73 | value = unic(value) if variable.startswith('$') else seq2str2(value)
74 | if cut_long and len(value) > _MAX_ASSIGN_LENGTH:
75 | value = value[:_MAX_ASSIGN_LENGTH] + '...'
76 | return '%s = %s' % (variable, value)
77 |
78 |
79 | def get_console_length(text):
80 | return sum(get_char_width(char) for char in text)
81 |
82 |
83 | def pad_console_length(text, width):
84 | if width < 5:
85 | width = 5
86 | diff = get_console_length(text) - width
87 | if diff <= 0:
88 | return _pad_width(text, width)
89 | return _pad_width(_lose_width(text, diff+3)+'...', width)
90 |
91 | def _pad_width(text, width):
92 | more = width - get_console_length(text)
93 | return text + ' ' * more
94 |
95 | def _lose_width(text, diff):
96 | lost = 0
97 | while lost < diff:
98 | lost += get_console_length(text[-1])
99 | text = text[:-1]
100 | return text
101 |
--------------------------------------------------------------------------------
/lib/robot/utils/unic.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import sys
16 |
17 |
18 | # Need different unic implementations for different Pythons because:
19 | # 1) Importing unicodedata module on Jython takes a very long time, and doesn't
20 | # seem to be necessary as Java probably already handles normalization.
21 | # Furthermore, Jython on Java 1.5 doesn't even have unicodedata.normalize.
22 | # 2) IronPython 2.6 doesn't have unicodedata and probably doesn't need it.
23 | # 3) CPython doesn't automatically normalize Unicode strings.
24 |
25 | if sys.platform.startswith('java'):
26 | from java.lang import Object, Class
27 | def unic(item, *args):
28 | # http://bugs.jython.org/issue1564
29 | if isinstance(item, Object) and not isinstance(item, Class):
30 | try:
31 | item = item.toString() # http://bugs.jython.org/issue1563
32 | except:
33 | return _unrepresentable_object(item)
34 | return _unic(item, *args)
35 |
36 | elif sys.platform == 'cli':
37 | def unic(item, *args):
38 | return _unic(item, *args)
39 |
40 | else:
41 | from unicodedata import normalize
42 | def unic(item, *args):
43 | return normalize('NFC', _unic(item, *args))
44 |
45 |
46 | def _unic(item, *args):
47 | # Based on a recipe from http://code.activestate.com/recipes/466341
48 | try:
49 | return unicode(item, *args)
50 | except UnicodeError:
51 | try:
52 | ascii_text = str(item).encode('string_escape')
53 | except:
54 | return _unrepresentable_object(item)
55 | else:
56 | return unicode(ascii_text)
57 | except:
58 | return _unrepresentable_object(item)
59 |
60 |
61 | def safe_repr(item):
62 | try:
63 | return unic(repr(item))
64 | except UnicodeError:
65 | return repr(unic(item))
66 | except:
67 | return _unrepresentable_object(item)
68 |
69 | if sys.platform == 'cli':
70 | # IronPython omits `u` prefix from `repr(u'foo')`. We add it back to have
71 | # consistent and easier to test log messages.
72 | _safe_repr = safe_repr
73 |
74 | def safe_repr(item):
75 | if isinstance(item, list):
76 | return '[%s]' % ', '.join(safe_repr(i) for i in item)
77 | ret = _safe_repr(item)
78 | if isinstance(item, unicode) and not ret.startswith('u'):
79 | ret = 'u' + ret
80 | return ret
81 |
82 |
83 | _unrepresentable_msg = u""
84 |
85 | def _unrepresentable_object(item):
86 | from robot.utils.error import get_error_message
87 | return _unrepresentable_msg % (item.__class__.__name__, get_error_message())
88 |
--------------------------------------------------------------------------------
/lib/robot/variables/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | """Implements handling and resolving of variables.
16 |
17 | This package is likely to change radically in RF 2.8. External code should use
18 | functionality provided directly by this package with care.
19 | """
20 |
21 | import os
22 | import tempfile
23 |
24 | from robot import utils
25 | from robot.output import LOGGER
26 |
27 | from .isvar import is_var, is_scalar_var, is_list_var
28 | from .variables import Variables
29 | from .variableassigner import VariableAssigner
30 | from .variablesplitter import VariableSplitter
31 |
32 |
33 | GLOBAL_VARIABLES = Variables()
34 |
35 |
36 | def init_global_variables(settings):
37 | GLOBAL_VARIABLES.clear()
38 | _set_cli_vars(settings)
39 | for name, value in [ ('${TEMPDIR}', utils.abspath(tempfile.gettempdir())),
40 | ('${EXECDIR}', utils.abspath('.')),
41 | ('${/}', os.sep),
42 | ('${:}', os.pathsep),
43 | ('${\\n}', os.linesep),
44 | ('${SPACE}', ' '),
45 | ('${EMPTY}', ''),
46 | ('@{EMPTY}', ()),
47 | ('${True}', True),
48 | ('${False}', False),
49 | ('${None}', None),
50 | ('${null}', None),
51 | ('${OUTPUT_DIR}', settings['OutputDir']),
52 | ('${OUTPUT_FILE}', settings['Output']),
53 | ('${REPORT_FILE}', settings['Report']),
54 | ('${LOG_FILE}', settings['Log']),
55 | ('${DEBUG_FILE}', settings['DebugFile']),
56 | ('${PREV_TEST_NAME}', ''),
57 | ('${PREV_TEST_STATUS}', ''),
58 | ('${PREV_TEST_MESSAGE}', '') ]:
59 | GLOBAL_VARIABLES[name] = value
60 |
61 |
62 | def _set_cli_vars(settings):
63 | for path, args in settings['VariableFiles']:
64 | try:
65 | GLOBAL_VARIABLES.set_from_file(path, args)
66 | except:
67 | msg, details = utils.get_error_details()
68 | LOGGER.error(msg)
69 | LOGGER.info(details)
70 | for varstr in settings['Variables']:
71 | try:
72 | name, value = varstr.split(':', 1)
73 | except ValueError:
74 | name, value = varstr, ''
75 | GLOBAL_VARIABLES['${%s}' % name] = value
76 |
--------------------------------------------------------------------------------
/lib/robot/variables/isvar.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 |
16 | def is_var(string):
17 | if not isinstance(string, basestring):
18 | return False
19 | length = len(string)
20 | return length > 3 and string[0] in ['$','@'] and string.rfind('{') == 1 \
21 | and string.find('}') == length - 1
22 |
23 |
24 | def is_scalar_var(string):
25 | return is_var(string) and string[0] == '$'
26 |
27 |
28 | def is_list_var(string):
29 | return is_var(string) and string[0] == '@'
30 |
--------------------------------------------------------------------------------
/lib/robot/version.py:
--------------------------------------------------------------------------------
1 | # Automatically generated by 'package.py' script.
2 |
3 | import sys
4 |
5 | VERSION = '2.7.5'
6 | RELEASE = 'final'
7 | TIMESTAMP = '20121024-155048'
8 |
9 | def get_version(sep=' '):
10 | if RELEASE == 'final':
11 | return VERSION
12 | return VERSION + sep + RELEASE
13 |
14 | def get_full_version(who=''):
15 | sys_version = sys.version.split()[0]
16 | version = '%s %s (%s %s on %s)' \
17 | % (who, get_version(), _get_interpreter(), sys_version, sys.platform)
18 | return version.strip()
19 |
20 | def _get_interpreter():
21 | if sys.platform.startswith('java'):
22 | return 'Jython'
23 | if sys.platform == 'cli':
24 | return 'IronPython'
25 | if 'PyPy' in sys.version:
26 | return 'PyPy'
27 | return 'Python'
28 |
--------------------------------------------------------------------------------
/lib/robot/writer/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | """Implements writing of parsed test data to files.
16 |
17 | This functionality is used by :py:meth:`robot.parsing.model.TestCaseFile.save`.
18 |
19 | This package is considered stable.
20 | """
21 |
22 | from .datafilewriter import DataFileWriter
23 |
--------------------------------------------------------------------------------
/lib/robot/writer/aligners.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from .dataextractor import DataExtractor
16 |
17 |
18 | class _Aligner(object):
19 |
20 | def __init__(self, widths=None):
21 | self._widths = widths or []
22 |
23 | def align_rows(self, rows):
24 | return [self.align_row(r) for r in rows]
25 |
26 | def align_row(self, row):
27 | for index, col in enumerate(row):
28 | if len(self._widths) <= index:
29 | break
30 | row[index] = row[index].ljust(self._widths[index])
31 | return row
32 |
33 |
34 | class FirstColumnAligner(_Aligner):
35 |
36 | def __init__(self, first_column_width):
37 | _Aligner.__init__(self, [first_column_width])
38 |
39 |
40 | class ColumnAligner(_Aligner):
41 |
42 | def __init__(self, first_column_width, table):
43 | _Aligner.__init__(self, self._count_widths(first_column_width, table))
44 |
45 | def _count_widths(self, first_column_width, table):
46 | result = [first_column_width] + [len(h) for h in table.header[1:]]
47 | for row in DataExtractor().rows_from_table(table):
48 | for index, col in enumerate(row[1:]):
49 | index += 1
50 | if len(result) <= index:
51 | result.append(len(col))
52 | else:
53 | result[index] = max(len(col), result[index])
54 | return result
55 |
56 |
57 | class NullAligner(_Aligner):
58 |
59 | def align_rows(self, rows):
60 | return rows
61 |
62 | def align_row(self, row):
63 | return row
64 |
--------------------------------------------------------------------------------
/lib/robot/writer/dataextractor.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 |
16 | class DataExtractor(object):
17 | """Transforms table of a parsed test data file into a list of rows."""
18 |
19 | def __init__(self, want_name_on_first_row=None):
20 | self._want_name_on_first_row = want_name_on_first_row or \
21 | (lambda t,n: False)
22 |
23 | def rows_from_table(self, table):
24 | if table.type in ['setting', 'variable']:
25 | return self._rows_from_item(table)
26 | return self._rows_from_indented_table(table)
27 |
28 | def _rows_from_indented_table(self, table):
29 | items = list(table)
30 | for index, item in enumerate(items):
31 | for row in self._rows_from_test_or_keyword(item, table):
32 | yield row
33 | if not self._last(items, index):
34 | yield []
35 |
36 | def _rows_from_test_or_keyword(self, test_or_keyword, table):
37 | rows = list(self._rows_from_item(test_or_keyword, 1))
38 | for r in self._add_name(test_or_keyword.name, rows, table):
39 | yield r
40 |
41 | def _add_name(self, name, rows, table):
42 | if rows and self._want_name_on_first_row(table, name):
43 | rows[0][0] = name
44 | return rows
45 | return [[name]] + rows
46 |
47 | def _rows_from_item(self, item, indent=0):
48 | for child in item:
49 | if child.is_set():
50 | yield [''] * indent + child.as_list()
51 | if child.is_for_loop():
52 | for row in self._rows_from_item(child, indent+1):
53 | yield row
54 |
55 | def _last(self, items, index):
56 | return index >= len(items) -1
57 |
--------------------------------------------------------------------------------
/lib/robot/writer/htmltemplate.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | TEMPLATE_START = """\
16 |
17 |
18 |
19 |
20 |
63 | %(NAME)s
64 |
65 |
66 | %(NAME)s
67 | """
68 | TEMPLATE_END = """
69 |
70 | """
71 |
--------------------------------------------------------------------------------
/lib/robot/writer/rowsplitter.py:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2012 Nokia Siemens Networks Oyj
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import itertools
16 |
17 |
18 | class RowSplitter(object):
19 | _comment_mark = '#'
20 | _empty_cell_escape = '${EMPTY}'
21 | _line_continuation = '...'
22 |
23 | def __init__(self, cols=8):
24 | self._cols = cols
25 |
26 | def split(self, row, indented_table=False):
27 | if not row:
28 | return [[]]
29 | return self._split_to_rows(row, indented_table)
30 |
31 | def _split_to_rows(self, data, indented_table):
32 | indent = len(list(itertools.takewhile(lambda x: x == '', data)))
33 | if indented_table:
34 | indent = max(indent, 1)
35 | rows = []
36 | while data:
37 | current, data = self._split(data)
38 | rows.append(self._escape_last_empty_cell(current))
39 | if data and indent + 1 < self._cols:
40 | data = self._indent(data, indent)
41 | return rows
42 |
43 | def _split(self, data):
44 | row, rest = data[:self._cols], data[self._cols:]
45 | self._in_comment = any(c for c in row if c.startswith( self._comment_mark))
46 | rest = self._add_line_continuation(rest)
47 | return row, rest
48 |
49 | def _escape_last_empty_cell(self, row):
50 | if not row[-1].strip():
51 | row[-1] = self._empty_cell_escape
52 | return row
53 |
54 | def _add_line_continuation(self, data):
55 | if data:
56 | if self._in_comment:
57 | data[0] = self._comment_mark + data[0]
58 | data = [self._line_continuation] + data
59 | return data
60 |
61 | def _indent(self, row, indent):
62 | return [''] * indent + row
63 |
--------------------------------------------------------------------------------
/lib/scanner_cache.py:
--------------------------------------------------------------------------------
1 | import hashlib
2 |
3 | class CacheEntry:
4 | def __init__(self, stored_hash, data):
5 | self.stored_hash = stored_hash
6 | self.data = data
7 |
8 | class ScannerCache:
9 | def __init__(self):
10 | self.cache = {}
11 |
12 | def compute_hash(self, file_path, preread_lines=None):
13 | md5 = hashlib.md5()
14 | if preread_lines is None:
15 | with open(file_path, 'rb') as f:
16 | preread_lines = f.readlines()
17 | for line in preread_lines:
18 | md5.update(line)
19 | return md5.digest()
20 |
21 | def get_cached_data(self, file_path, preread_lines=None):
22 | if not file_path in self.cache:
23 | return None, None
24 | entry = self.cache[file_path]
25 | try:
26 | current_hash = self.compute_hash(file_path, preread_lines)
27 | except IOError:
28 | # file is probably deleted, so remove the cached entry
29 | del self.cache[file_path]
30 | return None, None
31 | if entry.stored_hash != current_hash:
32 | return None, current_hash
33 | return entry.data, current_hash
34 |
35 | def put_data(self, file_path, data, precomputed_hash=None):
36 | if precomputed_hash is None:
37 | precomputed_hash = self.compute_hash(file_path)
38 |
39 | self.cache[file_path] = CacheEntry(precomputed_hash, data)
40 |
--------------------------------------------------------------------------------
/lib/stdlib_keywords.py:
--------------------------------------------------------------------------------
1 | import os
2 | import json
3 | import webbrowser
4 | import urllib
5 |
6 |
7 | keywords = {}
8 |
9 | class WebKeyword:
10 | def __init__(self, name, url, library_name, args, doc):
11 | self.name = library_name + '.' + name
12 | self.url = url
13 | self.description = [args, doc, url]
14 |
15 | def show_definition(self, view, views_to_center):
16 | webbrowser.open(self.url)
17 |
18 | def allow_unprompted_go_to(self):
19 | # Opening the browser would be inconvenient if the user misclicked
20 | return False
21 |
22 |
23 | def load(plugin_dir):
24 | keywords.clear()
25 |
26 | scan_dir = os.path.join(plugin_dir, 'stdlib_keywords')
27 | for root, dirs, files in os.walk(scan_dir):
28 | for file_path in filter(lambda f: f.endswith('.json'), files):
29 | library_name = file_path[:-5]
30 | file_path = os.path.join(root, file_path)
31 | with open(file_path, 'rb') as f:
32 | json_dict = json.load(f)
33 | url = json_dict['url']
34 | for keyword in json_dict['keywords']:
35 | name = keyword['name']
36 | args = keyword['args']
37 | doc = keyword['shortdoc']
38 | lower_name = name.lower()
39 | web_keyword = WebKeyword(name, url + '#' + urllib.quote(name), library_name, args, doc)
40 | if not keywords.has_key(lower_name):
41 | keywords[lower_name] = []
42 | keywords[lower_name].append(web_keyword)
43 |
44 |
45 | def search_keywords(name):
46 | lower_name = name.lower()
47 | if not keywords.has_key(lower_name):
48 | return []
49 |
50 | return keywords[lower_name]
51 |
--------------------------------------------------------------------------------
/lib/string_populator.py:
--------------------------------------------------------------------------------
1 | import sublime
2 |
3 | from robot.api import TestCaseFile
4 | from robot.parsing.populators import FromFilePopulator
5 |
6 | class FromStringPopulator(FromFilePopulator):
7 | def __init__(self, datafile, lines):
8 | super(FromStringPopulator, self).__init__(datafile)
9 | self.lines = lines
10 |
11 | def readlines(self):
12 | return self.lines
13 |
14 | def close(self):
15 | pass
16 |
17 | def _open(self, path):
18 | return self
19 |
20 | def populate_testcase_file(view):
21 | regions = view.split_by_newlines(sublime.Region(0, view.size()))
22 | lines = [view.substr(region).encode('ascii', 'replace') + '\n' for region in regions]
23 | test_case_file = TestCaseFile(source=view.file_name())
24 | FromStringPopulator(test_case_file, lines).populate(test_case_file.source)
25 | return test_case_file
26 |
27 | def populate_from_lines(lines, file_path):
28 | data_file = TestCaseFile(source=file_path)
29 | FromStringPopulator(data_file, lines).populate(file_path)
30 | return data_file
31 |
--------------------------------------------------------------------------------
/robot.JSON-tmLanguage:
--------------------------------------------------------------------------------
1 | {
2 | "comment": "\n Robot Framework syntax highlighting for txt files.\n ",
3 | "fileTypes": [
4 | "txt"
5 | ],
6 | "keyEquivalent": "^~R",
7 | "name": "Robot Framework .txt",
8 | "patterns": [
9 | {
10 | "comment": "start of a table",
11 | "begin": "(?i)^\\*+\\s*(settings?|metadata|(user )?keywords?|test ?cases?|variables?)",
12 | "end": "$",
13 | "name": "string.robot.header"
14 | },
15 | {
16 | "begin": "(?i)^\\s*\\[?Documentation\\]?",
17 | "end": "^(?!\\s*+\\.\\.\\.)",
18 | "name": "comment"
19 | },
20 | {
21 | "comment": "testcase settings",
22 | "match": "(?i)\\[(Arguments|Setup|Teardown|Precondition|Postcondition|Template|Return|Timeout)\\]",
23 | "name": "storage.type.method.robot"
24 | },
25 | {
26 | "begin": "(?i)\\[Tags\\]",
27 | "comment": "test tags",
28 | "end": "^(?!\\s*+\\.\\.\\.)",
29 | "name": "storage.type.method.robot",
30 | "patterns": [
31 | {
32 | "match": "^\\s*\\.\\.\\.",
33 | "name": "comment"
34 | }
35 | ]
36 | },
37 | {
38 | "match": "\\b([0-9]*(\\.[0-9]+)?)\\b",
39 | "name": "constant.numeric.robot"
40 | },
41 | {
42 | "comment": "${variables}. one backslash escapes the variable, two do not",
43 | "begin": "((? url, "keywords" => keywords}
21 | out_file = "#{name}.json"
22 | puts "writing to #{out_file}"
23 | File.open(out_file, 'wb') do |f|
24 | output = JSON.pretty_generate json, {:indent => ' '}
25 | f.write(output + "\n")
26 | end
27 | end
28 |
29 | def process_library(url)
30 | uri = URI.parse(url)
31 | http = Net::HTTP.new(uri.host, uri.port)
32 | response = http.request(Net::HTTP::Get.new(uri.request_uri))
33 | doc = Nokogiri::HTML(response.body)
34 |
35 | puts "processing #{url}"
36 | h1 = doc.css('h1').text
37 | if h1 == 'Opening library documentation failed'
38 | puts 'detected javascript doc format'
39 |
40 | doc.css('script').each do |script|
41 | if script.text.include? 'libdoc ='
42 | text = script.text.strip
43 | text.slice! 'libdoc = '
44 | text = text[0..-2] if text[-1] == ';'
45 |
46 | json = JSON.parse(text)
47 | keywords = json['keywords']
48 | keywords.each { |key| key.delete 'doc' }
49 |
50 | write_json json['name'], url, keywords
51 | puts
52 | end
53 | end
54 | end
55 | end
56 |
57 | urls.each do |url|
58 | process_library(url)
59 | end
60 |
--------------------------------------------------------------------------------