Revision: 4432
Author: robotframework
Date: Tue Dec  7 05:38:51 2010
Log: Give warning if named argument is used twice, issue 728
http://code.google.com/p/robotframework/source/detail?r=4432

Modified:
 /trunk/atest/robot/keywords/named_args/with_user_keywords.txt
 /trunk/atest/testdata/keywords/named_args/with_user_keywords.txt
 /trunk/src/robot/running/arguments.py
 /trunk/src/robot/running/userkeyword.py

=======================================
--- /trunk/atest/robot/keywords/named_args/with_user_keywords.txt Wed Apr 14 00:27:07 2010 +++ /trunk/atest/robot/keywords/named_args/with_user_keywords.txt Tue Dec 7 05:38:51 2010
@@ -35,3 +35,10 @@
 Default value with escaped content
     Check Test Case  ${TESTNAME}

+Same Argument Specified Twice
+    Check Test Case  ${TESTNAME}
+    Check Log Message  ${ERRORS.messages[0]}
+ ... Could not resolve named arguments because value for argument '\${first}' was given twice. WARN
+    Check Log Message  ${ERRORS.messages[1]}
+ ... Could not resolve named arguments because value for argument '\${second}' was given twice. WARN
+
=======================================
--- /trunk/atest/testdata/keywords/named_args/with_user_keywords.txt Wed Apr 14 00:27:07 2010 +++ /trunk/atest/testdata/keywords/named_args/with_user_keywords.txt Tue Dec 7 05:38:51 2010
@@ -48,6 +48,13 @@
 Default value with escaped content
     ${ret}=  Escaped default value  d4=\${nv}
     Should Be Equal  ${ret}  \${notvariable} \\\\ \n${SPACE}\${nv}
+
+Same Argument Specified Twice
+    ${ret}=  Two Kwargs  bar  first=foo
+    Should Be Equal  ${ret}  bar, first=foo
+    ${ret}=  Two Kwargs  second=foo  bar
+    Should Be Equal  ${ret}  second=foo, bar
+

 *** Keywords ***
 One Kwarg
=======================================
--- /trunk/src/robot/running/arguments.py       Thu Aug 26 03:02:21 2010
+++ /trunk/src/robot/running/arguments.py       Tue Dec  7 05:38:51 2010
@@ -16,7 +16,7 @@
 import inspect
 from array import ArrayType

-from robot.errors import DataError
+from robot.errors import DataError, FrameworkError
 from robot.variables import is_list_var, is_scalar_var
 from robot import utils

@@ -34,13 +34,13 @@
kw_or_lib_name, self._type)

     def resolve(self, args, variables, output=None):
-        posargs, namedargs = self._resolve(args, variables)
+        posargs, namedargs = self._resolve(args, variables, output)
         self.check_arg_limits(posargs, namedargs)
         self._tracelog_args(output, posargs, namedargs)
         return posargs, namedargs

-    def _resolve(self, args, variables):
-        return self._get_argument_resolver().resolve(args, variables)
+    def _resolve(self, args, variables, output):
+ return self._get_argument_resolver().resolve(args, output, variables)

     def check_arg_limits(self, args, namedargs={}):
         self._arg_limit_checker.check_arg_limits(args, namedargs)
@@ -185,7 +185,7 @@
         PythonKeywordArguments.__init__(self, argument_source, name)
         self._arg_resolution_index = arg_resolution_index

-    def _resolve(self, args, variables):
+    def _resolve(self, args, variables, output):
args = variables.replace_from_beginning(self._arg_resolution_index, args)
         return args, {}

@@ -261,31 +261,31 @@
     def _fill_missing_args(self, arguments, needed):
         return arguments + needed * [None]

-    def resolve(self, arguments, variables):
- positional, varargs, named = self._resolve_arg_usage(arguments, variables)
+    def resolve(self, arguments, variables, output):
+ positional, varargs, named = self._resolve_arg_usage(arguments, variables, output)
         self._arg_limit_checker.check_arg_limits(positional+varargs, named)
argument_values = self._resolve_arg_values(variables, named, positional)
         argument_values += varargs
self._arg_limit_checker.check_missing_args(argument_values, len(arguments))
         return argument_values

-    def _template_for(self, variables):
-        return [ _MissingArg() for _ in range(self.minargs) ] \
-                + variables.replace_list(list(self.defaults))
+    def _resolve_arg_usage(self, arguments, variables, output):
+        resolver = UserKeywordArgumentResolver(self)
+        positional, named = resolver.resolve(arguments, output=output)
+ positional, named = self._replace_variables(variables, positional, named)
+        return self._split_args_and_varargs(positional) + (named,)

     def _resolve_arg_values(self, variables, named, positional):
         template = self._template_for(variables)
         for name, value in named.items():
-            template[self.names.index(name)] = value
+            template.set_value(self.names.index(name), value)
         for index, value in enumerate(positional):
-            template[index] = value
-        return template
-
-    def _resolve_arg_usage(self, arguments, variables):
-        resolver = UserKeywordArgumentResolver(self)
-        positional, named = self._replace_variables(variables,
- *resolver.resolve(arguments))
-        return self._split_args_and_varargs(positional) + (named,)
+            template.set_value(index, value)
+        return template.as_list()
+
+    def _template_for(self, variables):
+        defaults = variables.replace_list(list(self.defaults))
+        return UserKeywordArgsTemplate(self.minargs, defaults)

     def _replace_variables(self, variables, positional, named):
         for name in named:
@@ -318,7 +318,23 @@

 class _MissingArg(object):
     def __getattr__(self, name):
-        raise RuntimeError
+        raise DataError
+
+
+class UserKeywordArgsTemplate(object):
+
+    def __init__(self, minargs, defaults):
+        self._template = [_MissingArg() for _ in range(minargs)] + defaults
+        self._already_set = set()
+
+    def set_value(self, idx, value):
+        if idx in self._already_set:
+            raise FrameworkError
+        self._already_set.add(idx)
+        self._template[idx] = value
+
+    def as_list(self):
+        return self._template


 class _ArgumentResolver(object):
@@ -327,32 +343,39 @@
         self._arguments = arguments
self._mand_arg_count = len(arguments.names) - len(arguments.defaults)

-    def resolve(self, values, variables=None):
-        positional, named = self._resolve_argument_usage(values)
+    def resolve(self, values, output, variables=None):
+        positional, named = self._resolve_argument_usage(values, output)
         return self._resolve_variables(positional, named, variables)

-    def _resolve_argument_usage(self, values):
-        positional = []
+    def _resolve_argument_usage(self, values, output):
         named = {}
-        named_args_allowed = True
-        for arg in reversed(self._optional(values)):
-            if named_args_allowed and self._is_named(arg):
-                name, value = self._parse_named(arg)
-                if name in named:
- raise RuntimeError('Keyword argument %s repeated.' % name)
-                named[name] = value
+        last_positional = self._get_last_positional_idx(values)
+        used_names = self._arguments.names[:last_positional]
+        for arg in values[last_positional:]:
+            name, value = self._parse_named(arg)
+            if name in named:
+                raise DataError('Keyword argument %s repeated.' % name)
+            if name in used_names:
+ output.warn("Could not resolve named arguments because value for "
+                            "argument '%s' was given twice." % name)
+                return values, {}
             else:
-                positional.append(arg)
-                named_args_allowed = False
-        positional = self._mandatory(values) + list(reversed(positional))
-        return positional, named
+                used_names.append(name)
+                named[name] = value
+        return values[:last_positional], named
+
+    def _get_last_positional_idx(self, values):
+        last_positional_idx = self._mand_arg_count
+        named_allowed = True
+        for arg in reversed(self._optional(values)):
+            if not (named_allowed and self._is_named(arg)):
+                named_allowed = False
+                last_positional_idx += 1
+        return last_positional_idx

     def _optional(self, values):
         return values[self._mand_arg_count:]

-    def _mandatory(self, values):
-        return values[:self._mand_arg_count]
-
     def _is_named(self, arg):
         if self._is_str_with_kwarg_sep(arg):
             name, _ = self._split_from_kwarg_sep(arg)
=======================================
--- /trunk/src/robot/running/userkeyword.py     Thu Jun  3 11:47:59 2010
+++ /trunk/src/robot/running/userkeyword.py     Tue Dec  7 05:38:51 2010
@@ -137,13 +137,13 @@
         return None

def _variable_resolving_run(self, context, variables, args_spec, argument_values):
-        resolved_arguments = args_spec.resolve(argument_values, variables)
+        resolved_arguments = args_spec.resolve(argument_values, variables,
+                                               context.output)
         self._execute(context, variables, args_spec, resolved_arguments)
         return self._get_return_value(variables)

     def _execute(self, context, variables, args_spec, resolved_arguments):
-        args_spec.set_variables(resolved_arguments, variables,
-                                     context.output)
+ args_spec.set_variables(resolved_arguments, variables, context.output)
         self._verify_keyword_is_valid()
         self.timeout.start()
         self.keywords.run(context)

Reply via email to