├── .gitignore ├── Comments.tmPreferences ├── Default (Linux).sublime-keymap ├── Default (Linux).sublime-mousemap ├── Default (OSX).sublime-keymap ├── Default (OSX).sublime-mousemap ├── Default (Windows).sublime-keymap ├── Default (Windows).sublime-mousemap ├── LICENSE ├── Main.sublime-menu ├── README.md ├── lib ├── keyword_parse.py ├── robot │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ └── logger.py │ ├── common │ │ ├── __init__.py │ │ ├── handlers.py │ │ ├── keyword.py │ │ ├── libraries.py │ │ ├── model.py │ │ └── statistics.py │ ├── conf │ │ ├── __init__.py │ │ └── settings.py │ ├── errors.py │ ├── htmldata │ │ ├── __init__.py │ │ ├── common │ │ │ ├── doc_formatting.css │ │ │ └── js_disabled.css │ │ ├── htmlfilewriter.py │ │ ├── jartemplate.py │ │ ├── jsonwriter.py │ │ ├── lib │ │ │ ├── jquery.min.js │ │ │ ├── jquery.tablesorter.min.js │ │ │ ├── jquery.tmpl.min.js │ │ │ └── jsxcompressor.js │ │ ├── libdoc │ │ │ ├── libdoc.css │ │ │ ├── libdoc.html │ │ │ └── print.css │ │ ├── normaltemplate.py │ │ ├── rebot │ │ │ ├── common.css │ │ │ ├── fileloading.js │ │ │ ├── log.css │ │ │ ├── log.html │ │ │ ├── log.js │ │ │ ├── model.js │ │ │ ├── print.css │ │ │ ├── report.css │ │ │ ├── report.html │ │ │ ├── testdata.js │ │ │ ├── util.js │ │ │ └── view.js │ │ ├── template.py │ │ └── testdoc │ │ │ └── testdoc.html │ ├── jarrunner.py │ ├── jythonworkarounds.py │ ├── libdoc.py │ ├── libdocpkg │ │ ├── __init__.py │ │ ├── builder.py │ │ ├── consoleviewer.py │ │ ├── htmlwriter.py │ │ ├── javabuilder.py │ │ ├── model.py │ │ ├── output.py │ │ ├── robotbuilder.py │ │ ├── specbuilder.py │ │ ├── writer.py │ │ └── xmlwriter.py │ ├── libraries │ │ ├── BuiltIn.py │ │ ├── Collections.py │ │ ├── DeprecatedBuiltIn.py │ │ ├── DeprecatedOperatingSystem.py │ │ ├── Dialogs.py │ │ ├── Easter.py │ │ ├── OperatingSystem.py │ │ ├── Remote.py │ │ ├── Reserved.py │ │ ├── Screenshot.py │ │ ├── String.py │ │ ├── Telnet.py │ │ ├── XML.py │ │ ├── __init__.py │ │ ├── dialogs_ipy.py │ │ ├── dialogs_jy.py │ │ └── dialogs_py.py │ ├── model │ │ ├── __init__.py │ │ ├── criticality.py │ │ ├── filter.py │ │ ├── itemlist.py │ │ ├── keyword.py │ │ ├── message.py │ │ ├── metadata.py │ │ ├── modelobject.py │ │ ├── namepatterns.py │ │ ├── statistics.py │ │ ├── stats.py │ │ ├── suitestatistics.py │ │ ├── tags.py │ │ ├── tagsetter.py │ │ ├── tagstatistics.py │ │ ├── testcase.py │ │ ├── testsuite.py │ │ ├── totalstatistics.py │ │ └── visitor.py │ ├── output │ │ ├── __init__.py │ │ ├── debugfile.py │ │ ├── filelogger.py │ │ ├── highlighting.py │ │ ├── listeners.py │ │ ├── logger.py │ │ ├── loggerhelper.py │ │ ├── monitor.py │ │ ├── output.py │ │ ├── pyloggingconf.py │ │ ├── stdoutlogsplitter.py │ │ └── xmllogger.py │ ├── parsing │ │ ├── __init__.py │ │ ├── comments.py │ │ ├── datarow.py │ │ ├── htmlreader.py │ │ ├── model.py │ │ ├── populators.py │ │ ├── restreader.py │ │ ├── settings.py │ │ ├── tablepopulators.py │ │ ├── tsvreader.py │ │ └── txtreader.py │ ├── pythonpathsetter.py │ ├── rebot.py │ ├── reporting │ │ ├── __init__.py │ │ ├── jsbuildingcontext.py │ │ ├── jsexecutionresult.py │ │ ├── jsmodelbuilders.py │ │ ├── jswriter.py │ │ ├── logreportwriters.py │ │ ├── outputwriter.py │ │ ├── resultwriter.py │ │ ├── stringcache.py │ │ └── xunitwriter.py │ ├── result │ │ ├── __init__.py │ │ ├── configurer.py │ │ ├── executionerrors.py │ │ ├── executionresult.py │ │ ├── keyword.py │ │ ├── keywordremover.py │ │ ├── message.py │ │ ├── messagefilter.py │ │ ├── resultbuilder.py │ │ ├── suiteteardownfailed.py │ │ ├── testcase.py │ │ ├── testsuite.py │ │ ├── visitor.py │ │ └── xmlelementhandlers.py │ ├── run.py │ ├── runner.py │ ├── running │ │ ├── __init__.py │ │ ├── arguments.py │ │ ├── context.py │ │ ├── defaultvalues.py │ │ ├── fixture.py │ │ ├── handlers.py │ │ ├── importer.py │ │ ├── javaargcoercer.py │ │ ├── keywords.py │ │ ├── model.py │ │ ├── namespace.py │ │ ├── outputcapture.py │ │ ├── runerrors.py │ │ ├── runkwregister.py │ │ ├── signalhandler.py │ │ ├── testlibraries.py │ │ ├── timeouts │ │ │ ├── __init__.py │ │ │ ├── stoppablethread.py │ │ │ ├── timeoutsignaling.py │ │ │ ├── timeoutthread.py │ │ │ └── timeoutwin.py │ │ └── userkeyword.py │ ├── testdoc.py │ ├── tidy.py │ ├── utils │ │ ├── __init__.py │ │ ├── application.py │ │ ├── argumentparser.py │ │ ├── asserts.py │ │ ├── charwidth.py │ │ ├── compress.py │ │ ├── connectioncache.py │ │ ├── encoding.py │ │ ├── encodingsniffer.py │ │ ├── error.py │ │ ├── escaping.py │ │ ├── etreewrapper.py │ │ ├── htmlformatters.py │ │ ├── importer.py │ │ ├── markuputils.py │ │ ├── markupwriters.py │ │ ├── match.py │ │ ├── misc.py │ │ ├── normalizing.py │ │ ├── robotenv.py │ │ ├── robotpath.py │ │ ├── robottime.py │ │ ├── setter.py │ │ ├── text.py │ │ └── unic.py │ ├── variables │ │ ├── __init__.py │ │ ├── isvar.py │ │ ├── variableassigner.py │ │ ├── variables.py │ │ └── variablesplitter.py │ ├── version.py │ └── writer │ │ ├── __init__.py │ │ ├── aligners.py │ │ ├── dataextractor.py │ │ ├── datafilewriter.py │ │ ├── filewriters.py │ │ ├── formatters.py │ │ ├── htmlformatter.py │ │ ├── htmltemplate.py │ │ └── rowsplitter.py ├── robot_scanner.py ├── scanner_cache.py ├── stdlib_keywords.py └── string_populator.py ├── plugin.py ├── robot.JSON-tmLanguage ├── robot.sublime-build ├── robot.tmLanguage └── stdlib_keywords ├── BuiltIn.json ├── Collections.json ├── Dialogs.json ├── OperatingSystem.json ├── Screenshot.json ├── Selenium2Library.json ├── SeleniumLibrary.json ├── String.json ├── Telnet.json ├── XML.json └── stdlib_json_gen.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | 3 | *.tmLanguage.cache -------------------------------------------------------------------------------- /Comments.tmPreferences: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | name 6 | Comments 7 | scope 8 | text.robot 9 | settings 10 | 11 | shellVariables 12 | 13 | 14 | name 15 | TM_COMMENT_START 16 | value 17 | # 18 | 19 | 20 | name 21 | TM_COMMENT_DISABLE_INDENT 22 | value 23 | no 24 | 25 | 26 | 27 | uuid 28 | 376CF370-8A7B-450A-895C-FD18B77957E3 29 | 30 | -------------------------------------------------------------------------------- /Default (Linux).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["alt+enter"], "command": "robot_go_to_keyword" } 3 | ] 4 | -------------------------------------------------------------------------------- /Default (Linux).sublime-mousemap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "button": "button1", "count": 1, "modifiers": ["alt"], 4 | "press_command": "drag_select", 5 | "command": "robot_go_to_keyword" 6 | } 7 | ] 8 | -------------------------------------------------------------------------------- /Default (OSX).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["alt+enter"], "command": "robot_go_to_keyword" } 3 | ] 4 | -------------------------------------------------------------------------------- /Default (OSX).sublime-mousemap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "button": "button1", "count": 1, "modifiers": ["alt"], 4 | "press_command": "drag_select", 5 | "command": "robot_go_to_keyword" 6 | } 7 | ] 8 | -------------------------------------------------------------------------------- /Default (Windows).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["alt+enter"], "command": "robot_go_to_keyword" } 3 | ] 4 | -------------------------------------------------------------------------------- /Default (Windows).sublime-mousemap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "button": "button1", "count": 1, "modifiers": ["alt"], 4 | "press_command": "drag_select", 5 | "command": "robot_go_to_keyword" 6 | } 7 | ] 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sublime-robot-plugin 2 | ==================== 3 | This project is a plugin for Sublime Text 2 that provides some conveniences for working with Robot Framework test files (.txt only). 4 | 5 | Installation 6 | ------------ 7 | 8 | The easiest way to install is to use [Package Control](http://wbond.net/sublime_packages/package_control) and search for 'Robot Framework'. 9 | 10 | Otherwise, open Sublime Text 2 and click `Preferences -> Browse Packages` to open the packages directory. Then create a directory named `Robot Framework` containing the contents of this repository. 11 | 12 | Features 13 | -------- 14 | 15 | * Syntax highlighting for Robot txt files, plus automatic detection/activation 16 | * alt+enter or alt+click to: 17 | * go to resource file at caret 18 | * go to user keyword at caret 19 | * go to builtin keyword at caret (opens browser) 20 | * ctrl+space to auto complete user keywords (must start on first word of keyword since sublime will break on spaces) 21 | * search for keywords in all project robot files (open folders) 22 | * run script from Cmd+B 23 | * Toggle Comments with Cmd+/ 24 | 25 | Screenshots 26 | ----------- 27 | 28 | -------------------------------------------------------------------------------- /lib/keyword_parse.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | def get_keyword_at_pos(line, col): 4 | length = len(line) 5 | 6 | if length == 0: 7 | return None 8 | 9 | # between spaces 10 | if ((col >= length or line[col] == ' ' or line[col] == "\t") 11 | and (col == 0 or line[col-1] == ' ' or line[col-1] == "\t")): 12 | return None 13 | 14 | # first look back until we find 2 spaces in a row, or reach the beginning 15 | i = col - 1 16 | while i >= 0: 17 | if line[i] == "\t" or ((line[i - 1] == ' ' or line[i - 1] == '|') and line[i] == ' '): 18 | break 19 | i -= 1 20 | begin = i + 1 21 | 22 | # now look forward or until the end 23 | i = col # previous included line[col] 24 | while i < length: 25 | if line[i] == "\t" or (line[i] == " " and len(line) > i and (line[i + 1] == " " or line[i + 1] == '|')): 26 | break 27 | i += 1 28 | end = i 29 | 30 | keyword = line[begin:end] 31 | 32 | return line[begin:end] 33 | 34 | class TestGetKeywordAtPos(unittest.TestCase): 35 | def test_edges(self): 36 | self.assertEqual(get_keyword_at_pos('', 0), None) 37 | self.assertEqual(get_keyword_at_pos('A', 0), 'A') 38 | self.assertEqual(get_keyword_at_pos('A', 1), 'A') 39 | for i in range(0, 3): 40 | self.assertEqual(get_keyword_at_pos('AB', i), 'AB') 41 | for i in range(0, 4): 42 | self.assertEqual(get_keyword_at_pos('A B', i), 'A B') 43 | self.assertEqual(get_keyword_at_pos(' A', 4), 'A') 44 | self.assertEqual(get_keyword_at_pos('A ', 0), 'A') 45 | 46 | def test_splitting(self): 47 | self.assertEqual(get_keyword_at_pos('ABC DEF', 1), 'ABC') 48 | self.assertEqual(get_keyword_at_pos('ABC DEF', 5), 'DEF') 49 | self.assertEqual(get_keyword_at_pos('ABC DEF', 6), 'DEF') 50 | self.assertEqual(get_keyword_at_pos(' ABC DEF ', 3), 'ABC') 51 | self.assertEqual(get_keyword_at_pos(' ABC DEF ', 8), 'DEF') 52 | 53 | def test_inbetween_spaces(self): 54 | self.assertEqual(get_keyword_at_pos('ABC DEF', 4), None) 55 | self.assertEqual(get_keyword_at_pos(' ', 0), None) 56 | self.assertEqual(get_keyword_at_pos(' ', 1), None) 57 | self.assertEqual(get_keyword_at_pos(' ', 2), None) 58 | self.assertEqual(get_keyword_at_pos(' A', 0), None) 59 | 60 | def test_samples(self): 61 | self.assertEqual(get_keyword_at_pos('This Is A Keyword', 3), 'This Is A Keyword') 62 | self.assertEqual(get_keyword_at_pos('This Is A Keyword', 17), 'This Is A Keyword') 63 | self.assertEqual(get_keyword_at_pos('Run Some Keyword', 11), 'Some Keyword') 64 | 65 | def test_tab_char(self): 66 | self.assertEqual(get_keyword_at_pos('Run\tSome Keyword', 5), 'Some Keyword') 67 | 68 | if __name__ == '__main__': 69 | unittest.main() 70 | -------------------------------------------------------------------------------- /lib/robot/__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 | import sys 15 | 16 | if 'robot.pythonpathsetter' not in sys.modules: 17 | from robot import pythonpathsetter as _ 18 | if sys.platform.startswith('java'): 19 | from robot import jythonworkarounds as _ 20 | 21 | from robot.rebot import rebot, rebot_cli 22 | #from robot.run import run, run_cli 23 | #from robot.version import get_version 24 | 25 | 26 | __all__ = ['run', 'run_cli', 'rebot', 'rebot_cli'] 27 | #__version__ = get_version() 28 | -------------------------------------------------------------------------------- /lib/robot/api/__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 | """This package exposes the public APIs of Robot Framework. 16 | 17 | Unless stated otherwise, the APIs exposed in this module are considered stable, 18 | and thus safe to use when building external tools on top of Robot Framework. 19 | 20 | Currently exposed APIs are: 21 | 22 | * :py:mod:`.logger` for test libraries' logging purposes. 23 | 24 | * :py:func:`~robot.result.resultbuilder.ExecutionResult` for reading 25 | execution results from XML output files. 26 | 27 | * :py:class:`~robot.parsing.model.TestCaseFile`, 28 | :py:class:`~robot.parsing.model.TestDataDirectory`, and 29 | :py:class:`~robot.parsing.model.ResourceFile` for parsing data files. 30 | In addition, a convenience factory function 31 | :py:func:`~robot.parsing.model.TestData` creates either 32 | :py:class:`~robot.parsing.model.TestCaseFile` or 33 | :py:class:`~robot.parsing.model.TestDataDirectory` based on the input. 34 | 35 | * :py:func:`~robot.running.model.TestSuite` for creating a 36 | test suite that can be executed. This API is going to change in 37 | Robot Framework 2.8. 38 | 39 | These names can be imported like this: 40 | 41 | .. code-block:: python 42 | 43 | from robot.api import 44 | 45 | See documentations of the individual APIs for more details. 46 | """ 47 | 48 | from robot.parsing import TestCaseFile, TestDataDirectory, ResourceFile, TestData 49 | from robot.result import ExecutionResult 50 | from robot.running import TestSuite 51 | -------------------------------------------------------------------------------- /lib/robot/common/__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 | """Base classes for test execution model. 16 | 17 | This code was earlier used also by test result processing modules but not 18 | anymore in RF 2.7. 19 | 20 | The whole package is likely to be removed in RF 2.8 when test execution model 21 | is refactored. No new code should depend on this package. 22 | """ 23 | 24 | from .model import BaseTestSuite, BaseTestCase 25 | from .keyword import BaseKeyword 26 | from .handlers import UserErrorHandler 27 | from .libraries import BaseLibrary 28 | from .statistics import Statistics 29 | -------------------------------------------------------------------------------- /lib/robot/common/handlers.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 | 18 | class UserErrorHandler: 19 | """Created if creating handlers fail -- running raises DataError. 20 | 21 | The idea is not to raise DataError at processing time and prevent all 22 | tests in affected test case file from executing. Instead UserErrorHandler 23 | is created and if it is ever run DataError is raised then. 24 | """ 25 | type = 'error' 26 | 27 | def __init__(self, name, error): 28 | self.name = self.longname = name 29 | self.doc = self.shortdoc = '' 30 | self._error = error 31 | self.timeout = '' 32 | 33 | def init_keyword(self, varz): 34 | pass 35 | 36 | def run(self, *args): 37 | raise DataError(self._error) 38 | -------------------------------------------------------------------------------- /lib/robot/common/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 | 16 | class BaseKeyword: 17 | 18 | def __init__(self, name='', args=None, doc='', timeout='', type='kw'): 19 | self.name = name 20 | self.args = args or [] 21 | self.doc = doc 22 | self.timeout = timeout 23 | self.type = type 24 | self.status = 'NOT_RUN' 25 | 26 | @property 27 | def passed(self): 28 | return self.status == 'PASS' 29 | 30 | def serialize(self, serializer): 31 | serializer.start_keyword(self) 32 | serializer.end_keyword(self) 33 | -------------------------------------------------------------------------------- /lib/robot/common/libraries.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 | from robot.errors import DataError 17 | 18 | 19 | class BaseLibrary: 20 | 21 | def get_handler(self, name): 22 | try: 23 | return self.handlers[name] 24 | except KeyError: 25 | raise DataError("No keyword handler with name '%s' found" % name) 26 | 27 | def has_handler(self, name): 28 | return self.handlers.has_key(name) 29 | 30 | def __len__(self): 31 | return len(self.handlers) 32 | -------------------------------------------------------------------------------- /lib/robot/conf/__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 | """Settings for both test execution and output processing.""" 16 | 17 | from .settings import RobotSettings, RebotSettings 18 | -------------------------------------------------------------------------------- /lib/robot/htmldata/__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 | """Generic functionality for writing output files in HTML format. 16 | 17 | :mod:`robot.reporting`, :mod:`robot.libdoc` and :mod:`robot.testdoc` use 18 | this package. 19 | """ 20 | 21 | from .htmlfilewriter import HtmlFileWriter, ModelWriter 22 | from .jsonwriter import JsonWriter 23 | 24 | LOG = 'rebot/log.html' 25 | REPORT = 'rebot/report.html' 26 | LIBDOC = 'libdoc/libdoc.html' 27 | TESTDOC = 'testdoc/testdoc.html' 28 | -------------------------------------------------------------------------------- /lib/robot/htmldata/common/doc_formatting.css: -------------------------------------------------------------------------------- 1 | .doc > * { 2 | margin: 1em 1em 0 1em; 3 | padding: 0; 4 | } 5 | .doc > p, .doc > h1, .doc > h2, .doc > h3, .doc > h4 { 6 | margin: 1em 0 0 0; 7 | } 8 | .doc > *:first-child { 9 | margin-top: 0; 10 | } 11 | .doc table { 12 | border: 1px solid gray; 13 | background: transparent; 14 | border-collapse: collapse; 15 | empty-cells: show; 16 | font-size: 0.9em; 17 | } 18 | .doc table td { 19 | border: 1px solid gray; 20 | padding: 0.1em 0.3em; 21 | height: 1.2em; 22 | } 23 | .doc pre { 24 | font-size: 1.1em; 25 | background: #F4F4FF; 26 | } 27 | .doc li { 28 | list-style-position: inside; 29 | list-style-type: square; 30 | } 31 | .doc img { 32 | border: 1px solid gray; 33 | } 34 | .doc hr { 35 | background: gray; 36 | height: 1px; 37 | border: 0; 38 | } 39 | -------------------------------------------------------------------------------- /lib/robot/htmldata/common/js_disabled.css: -------------------------------------------------------------------------------- 1 | #javascript_disabled { 2 | width: 600px; 3 | margin: 100px auto 0 auto; 4 | padding: 20px; 5 | color: #2A2A2E; 6 | border: 1px solid #9A9A9E; 7 | background: #FAFAFF; 8 | } 9 | #javascript_disabled h1 { 10 | width: 100%; 11 | } 12 | #javascript_disabled ul { 13 | font-size: 1.2em; 14 | } 15 | #javascript_disabled li { 16 | margin: 0.5em 0; 17 | } 18 | #javascript_disabled b { 19 | font-style: italic; 20 | } 21 | -------------------------------------------------------------------------------- /lib/robot/htmldata/jartemplate.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 | import os 17 | from posixpath import normpath, join 18 | from contextlib import contextmanager 19 | from java.io import BufferedReader, InputStreamReader 20 | 21 | # Works only when running from jar 22 | from org.robotframework.RobotRunner import getResourceAsStream 23 | 24 | 25 | class HtmlTemplate(object): 26 | _base_dir = '/Lib/robot/htmldata/' 27 | 28 | def __init__(self, filename): 29 | self._path = normpath(join(self._base_dir, filename.replace(os.sep, '/'))) 30 | 31 | def __iter__(self): 32 | with self._reader as reader: 33 | line = reader.readLine() 34 | while line is not None: 35 | yield line.rstrip() 36 | line = reader.readLine() 37 | 38 | @property 39 | @contextmanager 40 | def _reader(self): 41 | stream = getResourceAsStream(self._path) 42 | reader = BufferedReader(InputStreamReader(stream, 'UTF-8')) 43 | try: 44 | yield reader 45 | finally: 46 | reader.close() 47 | -------------------------------------------------------------------------------- /lib/robot/htmldata/libdoc/libdoc.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: white; 3 | color: black; 4 | font-size: small; 5 | font-family: sans-serif; 6 | padding: 0 0.5em; 7 | } 8 | .metadata th { 9 | text-align: left; 10 | padding-right: 1em; 11 | } 12 | a.name, span.name { 13 | font-style: italic; 14 | } 15 | a, a:link, a:visited { 16 | color: #c30; 17 | } 18 | a img { 19 | border: 1px solid #c30 !important; 20 | } 21 | a:hover, a:active { 22 | text-decoration: underline; 23 | color: black; 24 | } 25 | div.shortcuts { 26 | margin: 1em 0; 27 | font-size: 0.9em; 28 | } 29 | div.shortcuts a { 30 | text-decoration: none; 31 | white-space: nowrap; 32 | color: black; 33 | } 34 | div.shortcuts a:hover { 35 | text-decoration: underline; 36 | } 37 | table.keywords { 38 | border: 2px solid black; 39 | border-collapse: collapse; 40 | empty-cells: show; 41 | margin: 0.3em 0; 42 | width: 100%; 43 | } 44 | table.keywords th, table.keywords td { 45 | border: 2px solid black; 46 | padding: 0.2em; 47 | vertical-align: top; 48 | } 49 | table.keywords th { 50 | background: #bbb; 51 | color: black; 52 | } 53 | table.keywords td.kw { 54 | width: 150px; 55 | font-weight: bold; 56 | } 57 | table.keywords td.arg { 58 | width: 300px; 59 | font-style: italic; 60 | } 61 | .footer { 62 | font-size: 0.9em; 63 | } 64 | /* Docs originating from HTML and reST are wrapped to divs. */ 65 | .doc div > *:first-child { 66 | margin-top: 0; 67 | } 68 | .doc div > *:last-child { /* Does not work with IE8. */ 69 | margin-bottom: 0; 70 | } 71 | -------------------------------------------------------------------------------- /lib/robot/htmldata/libdoc/print.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-size: 8pt; 5 | } 6 | a { 7 | text-decoration: none; 8 | } 9 | -------------------------------------------------------------------------------- /lib/robot/htmldata/normaltemplate.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 | import os 17 | from os.path import abspath, dirname, join, normpath 18 | 19 | from robot.utils import utf8open 20 | 21 | class HtmlTemplate(object): 22 | _base_dir = join(dirname(abspath(__file__)), '..', 'htmldata') 23 | 24 | def __init__(self, filename): 25 | self._path = normpath(join(self._base_dir, filename.replace('/', os.sep))) 26 | 27 | def __iter__(self): 28 | with utf8open(self._path) as file: 29 | for line in file: 30 | yield line.rstrip() 31 | -------------------------------------------------------------------------------- /lib/robot/htmldata/rebot/fileloading.js: -------------------------------------------------------------------------------- 1 | window.fileLoading = (function () { 2 | 3 | var fileLoadingCallbacks = {}; 4 | 5 | var timestamp = new Date().getTime(); 6 | 7 | function loadKeywordsFile(filename, callback) { 8 | fileLoadingCallbacks[filename] = callback; 9 | var script = document.createElement('script'); 10 | script.type = 'text/javascript'; 11 | // timestamp as an argument to prevent browsers from caching scripts 12 | // see: http://stackoverflow.com/questions/866619/how-to-force-ie-to-reload-javascript 13 | script.src = filename+'?time='+timestamp; 14 | document.getElementsByTagName("head")[0].appendChild(script); 15 | } 16 | 17 | function getCallbackHandlerForKeywords(parent) { 18 | var callableList = []; 19 | return function (callable) { 20 | if (!parent.isChildrenLoaded) { 21 | callableList.push(callable); 22 | if (callableList.length == 1) { 23 | loadKeywordsFile(parent.childFileName, function () { 24 | parent.isChildrenLoaded = true; 25 | for (var i = 0; i < callableList.length; i++) { 26 | callableList[i](); 27 | } 28 | }); 29 | } 30 | } else { 31 | callable(); 32 | } 33 | } 34 | } 35 | 36 | function notifyFileLoaded(filename) { 37 | fileLoadingCallbacks[filename](); 38 | } 39 | 40 | return { 41 | getCallbackHandlerForKeywords: getCallbackHandlerForKeywords, 42 | notify: notifyFileLoaded 43 | } 44 | }()); 45 | -------------------------------------------------------------------------------- /lib/robot/htmldata/rebot/log.css: -------------------------------------------------------------------------------- 1 | /* Containers */ 2 | .suite, .test, .errors { 3 | border: 1px solid gray; 4 | padding: 0.3em; 5 | margin: 0.2em 0; 6 | } 7 | .test { 8 | border-style: dashed; 9 | } 10 | .errors, .metadata, .messages { 11 | width: 100%; 12 | } 13 | .keyword, .metadata, .messages { 14 | padding-left: 1.2em; 15 | } 16 | /* Suite, test and kw headers */ 17 | .elementheader { 18 | border: 1px solid white; 19 | border-radius: 2px; 20 | padding: 0.2em 0; 21 | } 22 | .elementheader:hover { 23 | cursor: pointer; 24 | background: #F4F4FF; 25 | border: 1px solid #D3D3D3; 26 | } 27 | .foldingbutton { 28 | float: left; 29 | height: 0.9em; 30 | width: 0.9em; 31 | line-height: 0.9em; 32 | text-align: center; 33 | font-size: 0.7em; 34 | margin: 0.3em 0.5em; 35 | border: 1px solid black; 36 | border-radius: 2px; 37 | } 38 | .foldingbutton:hover { 39 | background: yellow; 40 | } 41 | .name { 42 | font-weight: bold; 43 | } 44 | .expandall { 45 | float: right; 46 | margin: 0.2em 0.5em; 47 | font-size: 0.8em; 48 | } 49 | /* Messages and errors */ 50 | .messages { 51 | font-family: monospace; 52 | font-size: 1.2em; 53 | } 54 | .messages td, .errors td { 55 | vertical-align: top; 56 | } 57 | .time { 58 | width: 6em; 59 | } 60 | .error_time { 61 | width: 10em; 62 | white-space: nowrap; 63 | } 64 | .level { 65 | width: 5em; 66 | text-align: center; 67 | } 68 | /* Message tables - these MUST NOT be combined together because otherwise 69 | dynamically altering them based on visible log level is not possible. */ 70 | .trace_message { 71 | display: table; 72 | } 73 | .debug_message { 74 | display: table; 75 | } 76 | /* Metadata */ 77 | .metadata th { 78 | width: 13em; 79 | text-align: left; 80 | vertical-align: top; 81 | white-space: nowrap; 82 | padding-right: 0.5em; 83 | } 84 | .metadata td { 85 | vertical-align: top; 86 | } 87 | .keyword_metadata { 88 | font-size: 0.9em; 89 | } 90 | /* Custom styles for statistics */ 91 | #total_stats tr:hover, #tag_stats tr:hover { 92 | cursor: default; 93 | } 94 | -------------------------------------------------------------------------------- /lib/robot/htmldata/rebot/print.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: white !important; 3 | padding: 0; 4 | font-size: 8pt; 5 | } 6 | a:link, a:visited { 7 | color: black; 8 | } 9 | #header { 10 | width: auto; 11 | } 12 | .details, .statistics { 13 | width: 100%; 14 | } 15 | #generated_ago, #report_or_log_link, #normal_selector, .expand { 16 | display: none; 17 | } 18 | #print_selector { 19 | display: table-cell; 20 | } 21 | .narrow { 22 | padding: inherit !important; 23 | } 24 | .tablesorter-header { 25 | background-image: none; 26 | background: #DCDCF0 !important; 27 | } 28 | -------------------------------------------------------------------------------- /lib/robot/htmldata/rebot/report.css: -------------------------------------------------------------------------------- 1 | /* Generic table styles */ 2 | table { 3 | background: white; 4 | border: 1px solid black; 5 | border-collapse: collapse; 6 | empty-cells: show; 7 | margin: 0 1px; 8 | } 9 | th { 10 | background: #DCDCF0; 11 | color: black; 12 | } 13 | /* Summary and total/tag/suite details */ 14 | .details { 15 | clear: both; 16 | width: 63em; 17 | margin-bottom: 1em; 18 | } 19 | .details th { 20 | background: white; 21 | width: 10em; 22 | white-space: nowrap; 23 | text-align: left; 24 | vertical-align: top; 25 | padding: 0.2em 0.4em; 26 | } 27 | .details td { 28 | vertical-align: top; 29 | padding: 0.2em 0.4em; 30 | } 31 | #selector th, #selector td { 32 | padding-top: 0.5em; 33 | padding-bottom: 1em; 34 | white-space: normal; 35 | } 36 | #print_selector { 37 | display: none; 38 | } 39 | /* Tabs - adapted from http://www.htmldog.com/articles/tabs */ 40 | #detail_tabs { 41 | list-style: none; 42 | padding: 0; 43 | margin: 0 1em; 44 | } 45 | .detail_tab { 46 | float: left; 47 | background: #DCDCF0; 48 | border: 1px solid black; 49 | border-bottom-width: 0; 50 | margin: 0 0.5em 0 0; 51 | padding-top: 0.1em; 52 | border-radius: 3px 3px 0 0; 53 | } 54 | .detail_tab:hover { 55 | background: yellow; 56 | } 57 | .detail_tab a { 58 | color: black; 59 | text-decoration: none; 60 | font-weight: bold; 61 | padding: 0 1em; 62 | } 63 | .detail_tab_selected { 64 | position: relative; 65 | top: 1px; 66 | background: white; 67 | } 68 | .detail_tab_selected:hover { 69 | background: white; 70 | } 71 | /* Test details table */ 72 | #test_details { 73 | width: 100%; 74 | } 75 | #test_details tr:hover { 76 | background: #F4F4FF; 77 | } 78 | #test_details th, table#test_details td { 79 | border: 1px solid black; 80 | padding: 0 0.2em; 81 | } 82 | #test_details th { 83 | padding: 0.2em; 84 | } 85 | .details_col_name { 86 | min-width: 20em; 87 | font-weight: bold; 88 | } 89 | .details_col_doc { 90 | min-width: 10em; 91 | } 92 | .details_col_tags { 93 | min-width: 10em; 94 | } 95 | .details_col_crit { 96 | width: 2em; 97 | text-align: center; 98 | } 99 | .details_col_status { 100 | width: 3em; 101 | text-align: center; 102 | } 103 | .details_col_msg { 104 | min-width: 20em; 105 | } 106 | .details_col_elapsed { 107 | width: 5em; 108 | } 109 | .details_col_times { 110 | width: 9em; 111 | white-space: nowrap; 112 | } 113 | -------------------------------------------------------------------------------- /lib/robot/htmldata/template.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 | try: 16 | from .jartemplate import HtmlTemplate 17 | except ImportError: 18 | from .normaltemplate import HtmlTemplate 19 | -------------------------------------------------------------------------------- /lib/robot/jarrunner.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 org.robotframework import RobotRunner 16 | from robot import run_cli, rebot_cli 17 | from robot.libdoc import libdoc_cli 18 | from robot.tidy import tidy_cli 19 | from robot.testdoc import testdoc_cli 20 | 21 | 22 | USAGE = """robotframework.jar - Robot Framework runner. 23 | 24 | Usage: java -jar robotframework.jar [command] [options] [input(s)] 25 | 26 | Available commands: 27 | run - Run Robot Framework tests. The default, if no command is given. 28 | rebot - Post process Robot Framework output files. 29 | libdoc - Create test library or resource file documentation. 30 | tidy - Clean-up and changed format of test data files. 31 | testdoc - Create documentation from Robot Framework test data files. 32 | 33 | Run `java -jar robotframework.jar command --help` for more information about 34 | an individual command. 35 | 36 | Examples: 37 | java -jar robotframework.jar mytests.txt 38 | java -jar robotframework.jar run mytests.txt 39 | java -jar robotframework.jar rebot --log mylog.html out.xml 40 | java -jar robotframework.jar tidy --format txt mytests.html 41 | """ 42 | 43 | 44 | class JarRunner(RobotRunner): 45 | """Used for Java-Jython interop when RF is executed from .jar file""" 46 | _commands = {'run': run_cli, 'rebot': rebot_cli, 'tidy': tidy_cli, 47 | 'libdoc': libdoc_cli, 'testdoc': testdoc_cli} 48 | 49 | def run(self, args): 50 | try: 51 | self._run(args) 52 | except SystemExit, err: 53 | return err.code 54 | 55 | def _run(self, args): 56 | if not args or args[0] in ('-h', '--help'): 57 | print USAGE 58 | raise SystemExit(0) 59 | command, args = self._parse_command_line(args) 60 | command(args) # Always calls sys.exit() 61 | 62 | def _parse_command_line(self, args): 63 | try: 64 | return self._commands[args[0]], args[1:] 65 | except KeyError: 66 | return run_cli, args 67 | -------------------------------------------------------------------------------- /lib/robot/jythonworkarounds.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 | from java.lang import String 18 | 19 | 20 | # Global workaround for os.listdir bug http://bugs.jython.org/issue1593 21 | # This bug has been fixed in Jython 2.5.2. 22 | if sys.version_info[:3] < (2, 5, 2): 23 | os._orig_listdir = os.listdir 24 | def listdir(path): 25 | items = os._orig_listdir(path) 26 | if isinstance(path, unicode): 27 | items = [unicode(String(i).toString()) for i in items] 28 | return items 29 | os.listdir = listdir 30 | -------------------------------------------------------------------------------- /lib/robot/libdocpkg/__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 `libdoc` tool. 16 | 17 | For programmatic entry point, see :mod:`robot.libdoc`. 18 | 19 | This package is considered stable. 20 | """ 21 | 22 | from robot.errors import DataError 23 | from robot.utils import get_error_message 24 | 25 | from .builder import DocumentationBuilder 26 | from .consoleviewer import ConsoleViewer 27 | 28 | 29 | def LibraryDocumentation(library_or_resource, name=None, version=None, 30 | doc_format=None): 31 | builder = DocumentationBuilder(library_or_resource) 32 | try: 33 | libdoc = builder.build(library_or_resource) 34 | except DataError: 35 | raise 36 | except: 37 | raise DataError("Building library '%s' failed: %s" 38 | % (library_or_resource, get_error_message())) 39 | if name: 40 | libdoc.name = name 41 | if version: 42 | libdoc.version = version 43 | if doc_format: 44 | libdoc.doc_format = doc_format 45 | return libdoc 46 | -------------------------------------------------------------------------------- /lib/robot/libdocpkg/builder.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 | from robot.parsing import VALID_EXTENSIONS as RESOURCE_EXTENSIONS 19 | from robot.errors import DataError 20 | 21 | from .robotbuilder import LibraryDocBuilder, ResourceDocBuilder 22 | from .specbuilder import SpecDocBuilder 23 | if sys.platform.startswith('java'): 24 | from .javabuilder import JavaDocBuilder 25 | else: 26 | def JavaDocBuilder(): 27 | raise DataError('Documenting Java test libraries requires Jython.') 28 | 29 | 30 | def DocumentationBuilder(library_or_resource): 31 | extension = os.path.splitext(library_or_resource)[1][1:].lower() 32 | if extension in RESOURCE_EXTENSIONS: 33 | return ResourceDocBuilder() 34 | if extension == 'xml': 35 | return SpecDocBuilder() 36 | if extension == 'java': 37 | return JavaDocBuilder() 38 | return LibraryDocBuilder() 39 | -------------------------------------------------------------------------------- /lib/robot/libdocpkg/model.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.utils import setter 18 | 19 | from .writer import LibdocWriter 20 | from .output import LibdocOutput 21 | 22 | 23 | class LibraryDoc(object): 24 | 25 | def __init__(self, name='', doc='', version='', type='library', 26 | scope='', named_args=False, doc_format=''): 27 | self.name = name 28 | self.doc = doc 29 | self.version = version 30 | self.type = type 31 | self.scope = scope 32 | self.named_args = named_args 33 | self.doc_format = doc_format 34 | self.inits = [] 35 | self.keywords = [] 36 | 37 | @setter 38 | def scope(self, scope): 39 | return {'TESTCASE': 'test case', 40 | 'TESTSUITE': 'test suite', 41 | 'GLOBAL': 'global'}.get(scope, scope) 42 | 43 | @setter 44 | def doc_format(self, format): 45 | return format or 'ROBOT' 46 | 47 | @setter 48 | def keywords(self, kws): 49 | return sorted(kws) 50 | 51 | def save(self, output=None, format='HTML'): 52 | with LibdocOutput(output, format) as outfile: 53 | LibdocWriter(format).write(self, outfile) 54 | 55 | 56 | class KeywordDoc(object): 57 | 58 | def __init__(self, name='', args=None, doc=''): 59 | self.name = name 60 | self.args = args or [] 61 | self.doc = doc 62 | 63 | @property 64 | def shortdoc(self): 65 | return self.doc.splitlines()[0] if self.doc else '' 66 | 67 | def __cmp__(self, other): 68 | return cmp(self.name.lower(), other.name.lower()) 69 | -------------------------------------------------------------------------------- /lib/robot/libdocpkg/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 | import codecs 16 | 17 | 18 | class LibdocOutput(object): 19 | 20 | def __init__(self, output_path, format): 21 | self._output_path = output_path 22 | self._format = format.upper() 23 | self._output_file = None 24 | 25 | def __enter__(self): 26 | if self._format == 'HTML': 27 | self._output_file = codecs.open(self._output_path, 'w', 'UTF-8') 28 | return self._output_file 29 | return self._output_path 30 | 31 | def __exit__(self, *exc_info): 32 | if self._output_file: 33 | self._output_file.close() 34 | -------------------------------------------------------------------------------- /lib/robot/libdocpkg/specbuilder.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 | import os.path 17 | 18 | from robot.errors import DataError 19 | from robot.utils import ET, ETSource 20 | 21 | from .model import LibraryDoc, KeywordDoc 22 | 23 | 24 | class SpecDocBuilder(object): 25 | 26 | def build(self, path): 27 | spec = self._parse_spec(path) 28 | libdoc = LibraryDoc(name=spec.get('name'), 29 | type=spec.get('type'), 30 | version=spec.find('version').text or '', 31 | doc=spec.find('doc').text or '', 32 | scope=spec.find('scope').text or '', 33 | named_args=self._get_named_args(spec), 34 | doc_format=spec.get('format', 'ROBOT')) 35 | libdoc.inits = self._create_keywords(spec, 'init') 36 | libdoc.keywords = self._create_keywords(spec, 'kw') 37 | return libdoc 38 | 39 | def _parse_spec(self, path): 40 | if not os.path.isfile(path): 41 | raise DataError("Spec file '%s' does not exist." % path) 42 | with ETSource(path) as source: 43 | root = ET.parse(source).getroot() 44 | if root.tag != 'keywordspec': 45 | raise DataError("Invalid spec file '%s'." % path) 46 | return root 47 | 48 | def _get_named_args(self, spec): 49 | elem = spec.find('namedargs') 50 | if elem is None: 51 | return False # Backwards compatiblity with RF < 2.6.2 52 | return elem.text == 'yes' 53 | 54 | def _create_keywords(self, spec, path): 55 | return [self._create_keyword(elem) for elem in spec.findall(path)] 56 | 57 | def _create_keyword(self, elem): 58 | return KeywordDoc(name=elem.get('name', ''), 59 | args=[a.text for a in elem.findall('arguments/arg')], 60 | doc=elem.find('doc').text or '') 61 | -------------------------------------------------------------------------------- /lib/robot/libdocpkg/writer.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 .htmlwriter import LibdocHtmlWriter 18 | from .xmlwriter import LibdocXmlWriter 19 | 20 | 21 | def LibdocWriter(format=None): 22 | format = (format or 'HTML').upper() 23 | if format == 'HTML': 24 | return LibdocHtmlWriter() 25 | if format == 'XML': 26 | return LibdocXmlWriter() 27 | raise DataError("Format must be either 'HTML' or 'XML', got '%s'." % format) 28 | -------------------------------------------------------------------------------- /lib/robot/libdocpkg/xmlwriter.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 XmlWriter, get_timestamp 16 | 17 | 18 | class LibdocXmlWriter(object): 19 | 20 | def write(self, libdoc, outfile): 21 | writer = XmlWriter(outfile, encoding='UTF-8') 22 | writer.start('keywordspec', {'name': libdoc.name, 'type': libdoc.type, 23 | 'format': libdoc.doc_format, 24 | 'generated': get_timestamp(millissep=None)}) 25 | writer.element('version', libdoc.version) 26 | writer.element('scope', libdoc.scope) 27 | writer.element('namedargs', 'yes' if libdoc.named_args else 'no') 28 | writer.element('doc', libdoc.doc) 29 | self._write_keywords('init', libdoc.inits, writer) 30 | self._write_keywords('kw', libdoc.keywords, writer) 31 | writer.end('keywordspec') 32 | writer.close() 33 | 34 | def _write_keywords(self, type, keywords, writer): 35 | for kw in keywords: 36 | writer.start(type, {'name': kw.name} if type == 'kw' else {}) 37 | writer.start('arguments') 38 | for arg in kw.args: 39 | writer.element('arg', arg) 40 | writer.end('arguments') 41 | writer.element('doc', kw.doc) 42 | writer.end(type) 43 | -------------------------------------------------------------------------------- /lib/robot/libraries/DeprecatedOperatingSystem.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 OperatingSystem 17 | 18 | OPSYS = OperatingSystem.OperatingSystem() 19 | 20 | class DeprecatedOperatingSystem: 21 | ROBOT_LIBRARY_SCOPE = 'GLOBAL' 22 | 23 | delete_environment_variable = OPSYS.remove_environment_variable 24 | environment_variable_is_set = OPSYS.environment_variable_should_be_set 25 | environment_variable_is_not_set = OPSYS.environment_variable_should_not_be_set 26 | 27 | fail_unless_exists = OPSYS.should_exist 28 | fail_if_exists = OPSYS.should_not_exist 29 | fail_unless_file_exists = OPSYS.file_should_exist 30 | fail_if_file_exists = OPSYS.file_should_not_exist 31 | fail_unless_dir_exists = OPSYS.directory_should_exist 32 | fail_if_dir_exists = OPSYS.directory_should_not_exist 33 | fail_unless_dir_empty = OPSYS.directory_should_be_empty 34 | fail_if_dir_empty = OPSYS.directory_should_not_be_empty 35 | fail_unless_file_empty = OPSYS.file_should_be_empty 36 | fail_if_file_empty = OPSYS.file_should_not_be_empty 37 | 38 | empty_dir = OPSYS.empty_directory 39 | remove_dir = OPSYS.remove_directory 40 | copy_dir = OPSYS.copy_directory 41 | move_dir = OPSYS.move_directory 42 | create_dir = OPSYS.create_directory 43 | list_dir = OPSYS.list_directory 44 | list_files_in_dir = OPSYS.list_files_in_directory 45 | list_dirs_in_dir = OPSYS.list_directories_in_directory 46 | count_items_in_dir = OPSYS.count_items_in_directory 47 | count_files_in_dir = OPSYS.count_files_in_directory 48 | count_dirs_in_dir = OPSYS.count_directories_in_directory 49 | -------------------------------------------------------------------------------- /lib/robot/libraries/Dialogs.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 | """A test library providing dialogs for interacting with users. 16 | 17 | `Dialogs` is Robot Framework's standard library that provides means 18 | for pausing the test execution and getting input from users. The 19 | dialogs are slightly different depending on are tests run on Python or 20 | Jython but they provide the same functionality. 21 | 22 | The library has following two limitations: 23 | - It is not compatible with IronPython. 24 | - It cannot be used with timeouts on Python. 25 | """ 26 | 27 | import sys 28 | 29 | if sys.platform.startswith('java'): 30 | from dialogs_jy import MessageDialog, PassFailDialog, InputDialog, SelectionDialog 31 | elif sys.platform == 'cli': 32 | from dialogs_ipy import MessageDialog, PassFailDialog, InputDialog, SelectionDialog 33 | else: 34 | from dialogs_py import MessageDialog, PassFailDialog, InputDialog, SelectionDialog 35 | 36 | try: 37 | from robot.version import get_version 38 | except ImportError: 39 | __version__ = '' 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 | --------------------------------------------------------------------------------