Robot Framework Integrated Development Environment (RIDE)
steprunner.py
Go to the documentation of this file.
1 # Copyright 2008-2015 Nokia Networks
2 # Copyright 2016- Robot Framework Foundation
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 robotide.lib.robot.errors import (ExecutionFailed, ExecutionFailures, ExecutionPassed,
17  ExitForLoop, ContinueForLoop, DataError)
18 from robotide.lib.robot.result import Keyword as KeywordResult
19 from robotide.lib.robot.utils import (format_assign_message, frange, get_error_message,
20  is_list_like, is_number, plural_or_not as s, type_name)
21 from robotide.lib.robot.variables import is_scalar_var
22 
23 from .statusreporter import StatusReporter
24 
25 
26 class StepRunner():
27 
28  def __init__(self, context, templated=False):
29  self._context_context = context
30  self._templated_templated = bool(templated)
31 
32  def run_steps(self, steps):
33  errors = []
34  for step in steps:
35  try:
36  self.run_steprun_step(step)
37  except ExecutionPassed as exception:
38  exception.set_earlier_failures(errors)
39  raise exception
40  except ExecutionFailed as exception:
41  errors.extend(exception.get_errors())
42  if not exception.can_continue(self._context_context.in_teardown,
43  self._templated_templated,
44  self._context_context.dry_run):
45  break
46  if errors:
47  raise ExecutionFailures(errors)
48 
49  def run_step(self, step, name=None):
50  context = self._context_context
51  if step.type == step.FOR_LOOP_TYPE:
52  runner = ForRunner(context, self._templated_templated, step.flavor)
53  return runner.run(step)
54  runner = context.get_runner(name or step.name)
55  if context.dry_run:
56  return runner.dry_run(step, context)
57  return runner.run(step, context)
58 
59 
60 def ForRunner(context, templated=False, flavor='IN'):
61  runners = {'IN': ForInRunner,
62  'IN RANGE': ForInRangeRunner,
63  'IN ZIP': ForInZipRunner,
64  'IN ENUMERATE': ForInEnumerateRunner}
65  try:
66  runner = runners[flavor]
67  except KeyError:
68  return InvalidForRunner(context, flavor)
69  return runner(context, templated)
70 
71 
72 class ForInRunner():
73 
74  def __init__(self, context, templated=False):
75  self._context_context = context
76  self._templated_templated = templated
77 
78  def run(self, data, name=None):
79  result = KeywordResult(kwname=self._get_name_get_name(data),
80  type=data.FOR_LOOP_TYPE)
81  with StatusReporter(self._context_context, result):
82  self._validate_validate(data)
83  self._run_run(data)
84 
85  def _get_name(self, data):
86  return '%s %s [ %s ]' % (' | '.join(data.variables),
87  self._flavor_name_flavor_name(),
88  ' | '.join(data.values))
89 
90  def _flavor_name(self):
91  return 'IN'
92 
93  def _validate(self, data):
94  if not data.variables:
95  raise DataError('FOR loop has no loop variables.')
96  for var in data.variables:
97  if not is_scalar_var(var):
98  raise DataError("Invalid FOR loop variable '%s'." % var)
99  if not data.values:
100  raise DataError('FOR loop has no loop values.')
101  if not data.keywords:
102  raise DataError('FOR loop contains no keywords.')
103 
104  def _run(self, data):
105  errors = []
106  for values in self._get_values_for_one_round_get_values_for_one_round(data):
107  try:
108  self._run_one_round_run_one_round(data, values)
109  except ExitForLoop as exception:
110  if exception.earlier_failures:
111  errors.extend(exception.earlier_failures.get_errors())
112  break
113  except ContinueForLoop as exception:
114  if exception.earlier_failures:
115  errors.extend(exception.earlier_failures.get_errors())
116  continue
117  except ExecutionPassed as exception:
118  exception.set_earlier_failures(errors)
119  raise exception
120  except ExecutionFailed as exception:
121  errors.extend(exception.get_errors())
122  if not exception.can_continue(self._context_context.in_teardown,
123  self._templated_templated,
124  self._context_context.dry_run):
125  break
126  if errors:
127  raise ExecutionFailures(errors)
128 
129  def _get_values_for_one_round(self, data):
130  if not self._context_context.dry_run:
131  values = self._replace_variables_replace_variables(data)
132  var_count = self._values_per_iteration_values_per_iteration(data.variables)
133  for i in range(0, len(values), var_count):
134  yield values[i:i+var_count]
135  else:
136  yield data.variables
137 
138  def _replace_variables(self, data):
139  values = self._context_context.variables.replace_list(data.values)
140  values = self._transform_items_transform_items(values)
141  values_per_iteration = self._values_per_iteration_values_per_iteration(data.variables)
142  if len(values) % values_per_iteration == 0:
143  return values
144  self._raise_wrong_variable_count_raise_wrong_variable_count(values_per_iteration, len(values))
145 
146  def _raise_wrong_variable_count(self, variables, values):
147  raise DataError('Number of FOR loop values should be multiple of '
148  'its variables. Got %d variables but %d value%s.'
149  % (variables, values, s(values)))
150 
151  def _run_one_round(self, data, values):
152  name = ', '.join(format_assign_message(var, item)
153  for var, item in zip(data.variables, values))
154  result = KeywordResult(kwname=name,
155  type=data.FOR_ITEM_TYPE)
156  for var, value in zip(data.variables, values):
157  self._context_context.variables[var] = value
158  runner = StepRunner(self._context_context, self._templated_templated)
159  with StatusReporter(self._context_context, result):
160  runner.run_steps(data.keywords)
161 
162  def _transform_items(self, items):
163  return items
164 
165 
174  def _values_per_iteration(self, variables):
175  return len(variables)
176 
177 
179 
180  def __init__(self, context, templated=False):
181  super(ForInRangeRunner, self).__init__(context, templated)
182 
183  def _flavor_name(self):
184  return 'IN RANGE'
185 
186  def _transform_items(self, items):
187  try:
188  items = [self._to_number_with_arithmetics_to_number_with_arithmetics(item) for item in items]
189  except:
190  raise DataError('Converting argument of FOR IN RANGE failed: %s.'
191  % get_error_message())
192  if not 1 <= len(items) <= 3:
193  raise DataError('FOR IN RANGE expected 1-3 arguments, got %d.'
194  % len(items))
195  return frange(*items)
196 
198  if is_number(item):
199  return item
200  number = eval(str(item), {})
201  if not is_number(number):
202  raise TypeError("Expected number, got %s." % type_name(item))
203  return number
204 
205 
207 
208  def __init__(self, context, templated=False):
209  super(ForInZipRunner, self).__init__(context, templated)
210 
211  def _flavor_name(self):
212  return 'IN ZIP'
213 
214  def _replace_variables(self, data):
215  values = super(ForInZipRunner, self)._replace_variables(data)
216  if len(data.variables) == len(data.values):
217  return values
218  raise DataError('FOR IN ZIP expects an equal number of variables and '
219  'iterables. Got %d variable%s and %d iterable%s.'
220  % (len(data.variables), s(data.variables),
221  len(data.values), s(data.values)))
222 
223  def _transform_items(self, items):
224  answer = list()
225  for item in items:
226  if not is_list_like(item):
227  raise DataError('FOR IN ZIP items must all be list-like, '
228  'got %s.' % type_name(item))
229  for zipped_item in zip(*[list(item) for item in items]):
230  answer.extend(zipped_item)
231  return answer
232 
233 
235 
236  def __init__(self, context, templated=False):
237  super(ForInEnumerateRunner, self).__init__(context, templated)
238 
239  def _flavor_name(self):
240  return 'IN ENUMERATE'
241 
242  def _values_per_iteration(self, variables):
243  if len(variables) < 2:
244  raise DataError('FOR IN ENUMERATE expected 2 or more loop '
245  'variables, got %d.' % len(variables))
246  return len(variables) - 1
247 
248  def _get_values_for_one_round(self, data):
249  parent = super(ForInEnumerateRunner, self)
250  for index, values in enumerate(parent._get_values_for_one_round(data)):
251  yield [index] + values
252 
253  def _raise_wrong_variable_count(self, variables, values):
254  raise DataError('Number of FOR IN ENUMERATE loop values should be '
255  'multiple of its variables (excluding the index). '
256  'Got %d variable%s but %d value%s.'
257  % (variables, s(variables), values, s(values)))
258 
259 
260 
266 
267  def __init__(self, context, flavor):
268  super(InvalidForRunner, self).__init__(context, False)
269  self.flavorflavor = flavor
270 
271  def _run(self, data, *args, **kwargs):
272  raise DataError("Invalid FOR loop type '%s'. Expected 'IN', "
273  "'IN RANGE', 'IN ZIP', or 'IN ENUMERATE'."
274  % self.flavorflavor)
Used when variable does not exist.
Definition: errors.py:67
def __init__(self, context, templated=False)
Definition: steprunner.py:236
def __init__(self, context, templated=False)
Definition: steprunner.py:180
def __init__(self, context, templated=False)
Definition: steprunner.py:74
def _raise_wrong_variable_count(self, variables, values)
Definition: steprunner.py:146
def __init__(self, context, templated=False)
Definition: steprunner.py:208
Used to send an error from ForRunner() if it sees an unexpected error.
Definition: steprunner.py:265
def __init__(self, context, templated=False)
Definition: steprunner.py:28
def ForRunner(context, templated=False, flavor='IN')
Definition: steprunner.py:60
def get_error_message()
Returns error message of the last occurred exception.
Definition: error.py:41
def frange(*args)
Like range() but accepts float arguments.
Definition: frange.py:21
def format_assign_message(variable, value, cut_long=True)
Definition: text.py:94