2 new revisions:
Revision: 3067014f0e4c
Author: Pekka Klärck
Date: Sun Jun 19 05:51:50 2011
Log: _OutputSplitter deserves its own module and better name
http://code.google.com/p/robotframework/source/detail?r=3067014f0e4c
Revision: d94731670530
Author: Pekka Klärck
Date: Sun Jun 19 06:49:34 2011
Log: Tests and implementation for custom regexps with embedded
arguments....
http://code.google.com/p/robotframework/source/detail?r=d94731670530
==============================================================================
Revision: 3067014f0e4c
Author: Pekka Klärck
Date: Sun Jun 19 05:51:50 2011
Log: _OutputSplitter deserves its own module and better name
http://code.google.com/p/robotframework/source/detail?r=3067014f0e4c
Added:
/src/robot/output/stdoutlogsplitter.py
Modified:
/src/robot/output/output.py
=======================================
--- /dev/null
+++ /src/robot/output/stdoutlogsplitter.py Sun Jun 19 05:51:50 2011
@@ -0,0 +1,55 @@
+# Copyright 2008-2011 Nokia Siemens Networks Oyj
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import re
+
+from robot.output.loggerhelper import Message, LEVELS
+
+
+class StdoutLogSplitter(object):
+ """Splits messages logged through stdout (or stderr) into Message
objects"""
+
+ _split_from_levels = re.compile('^(?:\*'
+ '(%s|HTML)' # Level
+ '(:\d+(?:\.\d+)?)?' # Optional
timestamp
+ '\*)' % '|'.join(LEVELS), re.MULTILINE)
+
+ def __init__(self, output):
+ self._messages = list(self._get_messages(output.strip()))
+
+ def _get_messages(self, output):
+ for level, timestamp, msg in self._split_output(output):
+ if timestamp:
+ timestamp = self._format_timestamp(timestamp[1:])
+ yield Message(msg.strip(), level, timestamp=timestamp)
+
+ def _split_output(self, output):
+ tokens = self._split_from_levels.split(output)
+ tokens = self._add_initial_level_and_time_if_needed(tokens)
+ for i in xrange(0, len(tokens), 3):
+ yield tokens[i:i+3]
+
+ def _add_initial_level_and_time_if_needed(self, tokens):
+ if self._output_started_with_level(tokens):
+ return tokens[1:]
+ return ['INFO', None] + tokens
+
+ def _output_started_with_level(self, tokens):
+ return tokens[0] == ''
+
+ def _format_timestamp(self, millis):
+ return utils.format_time(float(millis)/1000, millissep='.')
+
+ def __iter__(self):
+ return iter(self._messages)
=======================================
--- /src/robot/output/output.py Sat Jun 18 14:14:55 2011
+++ /src/robot/output/output.py Sun Jun 19 05:51:50 2011
@@ -12,17 +12,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import re
-
from robot.common.statistics import Statistics
from robot import utils
import robot
-from loggerhelper import AbstractLogger, Message, LEVELS
+from loggerhelper import AbstractLogger
from logger import LOGGER
from xmllogger import XmlLogger
from listeners import Listeners
from debugfile import DebugFile
+from stdoutlogsplitter import StdoutLogSplitter
class Output(AbstractLogger):
@@ -71,8 +70,7 @@
LOGGER.end_keyword(kw)
def log_output(self, output):
- """Splits given output to levels and messages and logs them"""
- for msg in _OutputSplitter(output):
+ for msg in StdoutLogSplitter(output):
self.message(msg)
def message(self, msg):
@@ -80,39 +78,3 @@
def set_log_level(self, level):
return self._xmllogger.set_log_level(level)
-
-
-class _OutputSplitter:
- _split_from_levels = re.compile('^(?:\*'
- '(%s|HTML)' # Level
- '(:\d+(?:\.\d+)?)?' # Optional
timestamp
- '\*)' % '|'.join(LEVELS), re.MULTILINE)
-
- def __init__(self, output):
- self._messages = list(self._get_messages(output.strip()))
-
- def _get_messages(self, output):
- for level, timestamp, msg in self._split_output(output):
- if timestamp:
- timestamp = self._format_timestamp(timestamp[1:])
- yield Message(msg.strip(), level, timestamp=timestamp)
-
- def _split_output(self, output):
- tokens = self._split_from_levels.split(output)
- tokens = self._add_initial_level_and_time_if_needed(tokens)
- for i in xrange(0, len(tokens), 3):
- yield tokens[i:i+3]
-
- def _add_initial_level_and_time_if_needed(self, tokens):
- if self._output_started_with_level(tokens):
- return tokens[1:]
- return ['INFO', None] + tokens
-
- def _output_started_with_level(self, tokens):
- return tokens[0] == ''
-
- def _format_timestamp(self, millis):
- return utils.format_time(float(millis)/1000, millissep='.')
-
- def __iter__(self):
- return iter(self._messages)
==============================================================================
Revision: d94731670530
Author: Pekka Klärck
Date: Sun Jun 19 06:49:34 2011
Log: Tests and implementation for custom regexps with embedded
arguments.
Update issue 854
Owner: pekka.klarck
Status: Started
I had some extra time and decided to go ahead and implement this.
It turned out that hangling invalid regexps (e.g. "I do ${x:[}") and
regexps containing groups (e.g. "I do ${x:(a|b)c}") was somewhat more
difficult than I had thought.
The problem with the groups was that the overall regexp matching the
keyword names has capturing groups, and groups in the custom regexps
broke the logic of setting the values of the matched groups to
variables. To fix that I decided to make the groups in custom regexps
automatically non-capturing by adding ?: to them.
Adding ?: to all groups broke the possiblity to use other regexp
extensions in the custom regexps. Fixing that would have been way too
big a task compared to benefits. The match is already case-insensitive
and other features available via extensions don't make much sense in
this context. Instead creating keywords with regexp extensions now
fails with a clear error message.
Remaining task is updating the User Guide which I will do later today.
Any kind of testing and comments regarding the new functionality is
also appreciated.
http://code.google.com/p/robotframework/source/detail?r=d94731670530
Modified:
/atest/robot/keywords/embedded_arguments.txt
/atest/testdata/keywords/embedded_arguments.txt
/src/robot/running/userkeyword.py
=======================================
--- /atest/robot/keywords/embedded_arguments.txt Fri Jun 10 15:37:06 2011
+++ /atest/robot/keywords/embedded_arguments.txt Sun Jun 19 06:49:34 2011
@@ -26,6 +26,18 @@
${tc} = Check Test Case ${TEST NAME}
Should Be Equal ${tc.kws[0].name} User \${non existing} Selects
\${variables} From Webshop
+Custom Embedded Argument Regexp
+ Check Test Case ${TEST NAME}
+
+Grouping Custom Regexp
+ Check Test Case ${TEST NAME}
+
+Regexp Extensions Are Not Supported
+ Check Log Message ${ERRORS.msgs[0]} Creating user keyword 'Regexp
extensions like \${x:(?x)re} are not supported' failed: Regexp extensions
are not allowed in embedded arguments. ERROR
+
+Invalid Custom Regexp
+ Check Log Message ${ERRORS.msgs[1]} Creating user keyword 'Invalid
\${x:[} Regexp' failed: Compiling embedded arguments regexp failed: *
ERROR pattern=yes
+
Escaping Values Given As Embedded Arguments
${tc} = Check Test Case ${TEST NAME}
Should Be Equal ${tc.kws[0].name} \${name}, \${item} = User
\\\${nonex} Selects \\\\ From Webshop
=======================================
--- /atest/testdata/keywords/embedded_arguments.txt Fri Jun 10 15:37:06 2011
+++ /atest/testdata/keywords/embedded_arguments.txt Sun Jun 19 06:49:34 2011
@@ -27,11 +27,25 @@
[Documentation] FAIL Non-existing variable '${non existing}'.
User ${non existing} Selects ${variables} From Webshop
+Custom Embedded Argument Regexp
+ [Documentation] FAIL No keyword with name 'Result of a + b is fail'
found.
+ I execute "foo"
+ I execute "bar" with "zap"
+ Result of 1 + 1 is 2
+ Result of 43 - 1 is 42
+ Result of a + b is fail
+
+Grouping Custom Regexp
+ ${matches} = Grouping Custom Regexp(erts)
+ Should Be Equal ${matches} Custom-Regexp(erts)
+ ${matches} = Grouping Cuts Regexperts
+ Should Be Equal ${matches} Cuts-Regexperts
+
Escaping Values Given As Embedded Arguments
${name} ${item} = User \${nonex} Selects \\ From Webshop
Should Be Equal ${name}-${item} \${nonex}-\\
${name} ${item} = User \ Selects \ \ From Webshop
- Should Be Equal ${name}-${item} -${SPACE}
+ Should Be Equal ${name}-${item} ${EMPTY}-${SPACE}
Embedded Arguments Syntax Is Case Insensitive
x Gets y From The z
@@ -96,10 +110,10 @@
Log This is always executed
[Return] ${user} ${item}
-${Given/When/Then} this "${item}" ${no good name for this arg ...}
+${prefix:Given|When|Then} this "${item}" ${no good name for this arg ...}
Log ${item}-${no good name for this arg ...}
-${x} gets ${y} from the ${z}
+${x:x} gets ${y:\w} from the ${z:.}
Should Be Equal ${x}-${y}-${z} x-y-z
Keyword with ${variable} and normal args
@@ -117,3 +131,21 @@
${a}+tc+${b}
Log ${a}+tc+${b}
+I execute "${x:[^"]*}"
+ Should Be Equal ${x} foo
+
+I execute "${x}" with "${y}"
+ Should Be Equal ${x} bar
+ Should Be Equal ${y} zap
+
+Result of ${a:\d+} ${operator:[+-]} ${b:\d+} is ${result}
+ Should Be True ${a} ${operator} ${b} == ${result}
+
+Grouping ${x:Cu(st|ts)(om)?} ${y:Regexp\(?erts\)?}
+ [Return] ${x}-${y}
+
+Regexp extensions like ${x:(?x)re} are not supported
+ This is not executed
+
+Invalid ${x:[} Regexp
+ This is not executed
=======================================
--- /src/robot/running/userkeyword.py Sat Jun 11 14:51:08 2011
+++ /src/robot/running/userkeyword.py Sun Jun 19 06:49:34 2011
@@ -18,6 +18,7 @@
from robot.common import BaseLibrary, UserErrorHandler
from robot.errors import DataError, ExecutionFailed,
UserKeywordExecutionFailed
from robot.variables import is_list_var, VariableSplitter
+from robot.output import LOGGER
from robot import utils
from keywords import Keywords
@@ -34,11 +35,15 @@
self.name = self._get_name_for_resource_file(path)
self.handlers = utils.NormalizedDict(ignore=['_'])
self.embedded_arg_handlers = []
- for user_keyword in user_keywords:
+ for kw in user_keywords:
try:
- handler = EmbeddedArgsTemplate(user_keyword, self.name)
+ handler = EmbeddedArgsTemplate(kw, self.name)
+ except DataError, err:
+ LOGGER.error("Creating user keyword '%s' failed: %s"
+ % (kw.name, unicode(err)))
+ continue
except TypeError:
- handler = UserKeywordHandler(user_keyword, self.name)
+ handler = UserKeywordHandler(kw, self.name)
else:
self.embedded_arg_handlers.append(handler)
if handler.name in self.handlers:
@@ -192,6 +197,8 @@
class EmbeddedArgsTemplate(UserKeywordHandler):
+ _regexp_group = re.compile(r'(?<!\\)\(')
+ _regexp_extension = re.compile(r'(?<!\\)\(\?')
def __init__(self, keyword, libname):
if keyword.args.value:
@@ -204,22 +211,39 @@
def _read_embedded_args_and_regexp(self, string):
args = []
- regexp = ['^']
+ full_regexp = ['^']
while True:
before, variable, rest = self._split_from_variable(string)
if before is None:
break
- args.append(variable)
- regexp.extend([re.escape(before), '(.*?)'])
+ variable, regexp = self._get_regexp(variable)
+ args.append('${%s}' % variable)
+ full_regexp.extend([re.escape(before), '(%s)' % regexp])
string = rest
- regexp.extend([re.escape(rest), '$'])
- return args, re.compile(''.join(regexp), re.IGNORECASE)
+ full_regexp.extend([re.escape(rest), '$'])
+ return args, self._compile_regexp(''.join(full_regexp))
def _split_from_variable(self, string):
var = VariableSplitter(string, identifiers=['$'])
if var.identifier is None:
return None, None, string
- return string[:var.start], string[var.start:var.end],
string[var.end:]
+ return string[:var.start], var.base, string[var.end:]
+
+ def _get_regexp(self, variable):
+ if ':' not in variable:
+ return variable, '.*?'
+ variable, regexp = variable.split(':', 1)
+ if self._regexp_extension.search(regexp):
+ raise DataError('Regexp extensions are not allowed in
embedded '
+ 'arguments.')
+ return variable, self._regexp_group.sub('(?:', regexp)
+
+ def _compile_regexp(self, pattern):
+ try:
+ return re.compile(pattern, re.IGNORECASE)
+ except:
+ raise DataError("Compiling embedded arguments regexp
failed: %s"
+ % utils.get_error_message())
class EmbeddedArgs(UserKeywordHandler):