Robot Framework
transformers.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 ast import NodeVisitor
17 
18 from robot.output import LOGGER
19 from robot.variables import VariableIterator
20 
21 from .settings import Defaults, TestSettings
22 
23 
24 class SettingsBuilder(NodeVisitor):
25 
26  def __init__(self, suite, defaults):
27  self.suitesuite = suite
28  self.defaultsdefaults = defaults
29 
30  def visit_Documentation(self, node):
31  self.suitesuite.doc = node.value
32 
33  def visit_Metadata(self, node):
34  self.suitesuite.metadata[node.name] = node.value
35 
36  def visit_SuiteSetup(self, node):
37  self.suitesuite.setup.config(name=node.name, args=node.args,
38  lineno=node.lineno)
39 
40  def visit_SuiteTeardown(self, node):
41  self.suitesuite.teardown.config(name=node.name, args=node.args,
42  lineno=node.lineno)
43 
44  def visit_TestSetup(self, node):
45  self.defaultsdefaults.setup = {
46  'name': node.name, 'args': node.args, 'lineno': node.lineno
47  }
48 
49  def visit_TestTeardown(self, node):
50  self.defaultsdefaults.teardown = {
51  'name': node.name, 'args': node.args, 'lineno': node.lineno
52  }
53 
54  def visit_TestTimeout(self, node):
55  self.defaultsdefaults.timeout = node.value
56 
57  def visit_DefaultTags(self, node):
58  self.defaultsdefaults.default_tags = node.values
59 
60  def visit_ForceTags(self, node):
61  self.defaultsdefaults.force_tags = node.values
62 
63  def visit_KeywordTags(self, node):
64  self.defaultsdefaults.keyword_tags = node.values
65 
66  def visit_TestTemplate(self, node):
67  self.defaultsdefaults.template = node.value
68 
69  def visit_ResourceImport(self, node):
70  self.suitesuite.resource.imports.create(type='Resource', name=node.name,
71  lineno=node.lineno)
72 
73  def visit_LibraryImport(self, node):
74  self.suitesuite.resource.imports.create(type='Library', name=node.name,
75  args=node.args, alias=node.alias,
76  lineno=node.lineno)
77 
78  def visit_VariablesImport(self, node):
79  self.suitesuite.resource.imports.create(type='Variables', name=node.name,
80  args=node.args, lineno=node.lineno)
81 
82  def visit_VariableSection(self, node):
83  pass
84 
85  def visit_TestCaseSection(self, node):
86  pass
87 
88  def visit_KeywordSection(self, node):
89  pass
90 
91 
92 class SuiteBuilder(NodeVisitor):
93 
94  def __init__(self, suite, defaults):
95  self.suitesuite = suite
96  self.defaultsdefaults = defaults
97 
98  def visit_SettingSection(self, node):
99  pass
100 
101  def visit_Variable(self, node):
102  self.suite.resource.variables.create(name=node.name,
103  value=node.value,
104  lineno=node.lineno,
105  error=format_error(node.errors))
106 
107  def visit_TestCase(self, node):
108  TestCaseBuilder(self.suitesuite, self.defaultsdefaults).visit(node)
109 
110  def visit_Keyword(self, node):
111  KeywordBuilder(self.suitesuite.resource, self.defaultsdefaults).visit(node)
112 
113 
114 class ResourceBuilder(NodeVisitor):
115 
116  def __init__(self, resource):
117  self.resourceresource = resource
118  self.defaultsdefaults = Defaults()
119 
120  def visit_Documentation(self, node):
121  self.resourceresource.doc = node.value
122 
123  def visit_KeywordTags(self, node):
124  self.defaultsdefaults.keyword_tags = node.values
125 
126  def visit_LibraryImport(self, node):
127  self.resourceresource.imports.create(type='Library', name=node.name,
128  args=node.args, alias=node.alias,
129  lineno=node.lineno)
130 
131  def visit_ResourceImport(self, node):
132  self.resourceresource.imports.create(type='Resource', name=node.name,
133  lineno=node.lineno)
134 
135  def visit_VariablesImport(self, node):
136  self.resourceresource.imports.create(type='Variables', name=node.name,
137  args=node.args, lineno=node.lineno)
138 
139  def visit_Variable(self, node):
140  self.resourceresource.variables.create(name=node.name,
141  value=node.value,
142  lineno=node.lineno,
143  error=format_error(node.errors))
144 
145  def visit_Keyword(self, node):
146  KeywordBuilder(self.resourceresource, self.defaultsdefaults).visit(node)
147 
148 
149 class TestCaseBuilder(NodeVisitor):
150 
151  def __init__(self, suite, defaults):
152  self.suitesuite = suite
153  self.settingssettings = TestSettings(defaults)
154  self.testtest = None
155 
156  def visit_TestCase(self, node):
157  self.testtest = self.suitesuite.tests.create(name=node.name, lineno=node.lineno)
158  self.generic_visit(node)
159  self._set_settings_set_settings(self.testtest, self.settingssettings)
160 
161  def _set_settings(self, test, settings):
162  if settings.setup:
163  test.setup.config(**settings.setup)
164  if settings.teardown:
165  test.teardown.config(**settings.teardown)
166  if settings.timeout:
167  test.timeout = settings.timeout
168  if settings.tags:
169  test.tags = settings.tags
170  if settings.template:
171  test.template = settings.template
172  self._set_template_set_template(test, settings.template)
173 
174  def _set_template(self, parent, template):
175  for item in parent.body:
176  if item.type == item.FOR:
177  self._set_template_set_template(item, template)
178  elif item.type == item.IF_ELSE_ROOT:
179  for branch in item.body:
180  self._set_template_set_template(branch, template)
181  elif item.type == item.KEYWORD:
182  name, args = self._format_template_format_template(template, item.args)
183  item.name = name
184  item.args = args
185 
186  def _format_template(self, template, arguments):
187  variables = VariableIterator(template, identifiers='$')
188  count = len(variables)
189  if count == 0 or count != len(arguments):
190  return template, arguments
191  temp = []
192  for (before, _, after), arg in zip(variables, arguments):
193  temp.extend([before, arg])
194  temp.append(after)
195  return ''.join(temp), ()
196 
197  def visit_For(self, node):
198  ForBuilder(self.testtest).build(node)
199 
200  def visit_While(self, node):
201  WhileBuilder(self.testtest).build(node)
202 
203  def visit_If(self, node):
204  IfBuilder(self.testtest).build(node)
205 
206  def visit_Try(self, node):
207  TryBuilder(self.testtest).build(node)
208 
209  def visit_TemplateArguments(self, node):
210  self.testtest.body.create_keyword(args=node.args, lineno=node.lineno)
211 
212  def visit_Documentation(self, node):
213  self.testtest.doc = node.value
214 
215  def visit_Setup(self, node):
216  self.settingssettings.setup = {
217  'name': node.name, 'args': node.args, 'lineno': node.lineno
218  }
219 
220  def visit_Teardown(self, node):
221  self.settingssettings.teardown = {
222  'name': node.name, 'args': node.args, 'lineno': node.lineno
223  }
224 
225  def visit_Timeout(self, node):
226  self.settingssettings.timeout = node.value
227 
228  def visit_Tags(self, node):
229  deprecate_tags_starting_with_hyphen(node, self.suitesuite.source)
230  self.settingssettings.tags = node.values
231 
232  def visit_Template(self, node):
233  self.settingssettings.template = node.value
234 
235  def visit_KeywordCall(self, node):
236  self.testtest.body.create_keyword(name=node.keyword, args=node.args,
237  assign=node.assign, lineno=node.lineno)
238 
239  def visit_ReturnStatement(self, node):
240  self.testtest.body.create_return(node.values, lineno=node.lineno,
241  error=format_error(node.errors))
242 
243  def visit_Continue(self, node):
244  self.testtest.body.create_continue(lineno=node.lineno,
245  error=format_error(node.errors))
246 
247  def visit_Break(self, node):
248  self.testtest.body.create_break(lineno=node.lineno,
249  error=format_error(node.errors))
250 
251 
252 class KeywordBuilder(NodeVisitor):
253 
254  def __init__(self, resource, defaults):
255  self.resourceresource = resource
256  self.defaultsdefaults = defaults
257  self.kwkw = None
258 
259  def visit_Keyword(self, node):
260  self.kwkw = self.resourceresource.keywords.create(name=node.name,
261  tags=self.defaultsdefaults.keyword_tags,
262  lineno=node.lineno)
263  self.generic_visit(node)
264 
265  def visit_Documentation(self, node):
266  self.kwkw.doc = node.value
267 
268  def visit_Arguments(self, node):
269  self.kwkw.args = node.values
270  if node.errors:
271  error = format_error(node.errors)
272  self.kwkw.error = f'Invalid argument specification: {error}'
273 
274  def visit_Tags(self, node):
275  deprecate_tags_starting_with_hyphen(node, self.resourceresource.source)
276  self.kwkw.tags.add(node.values)
277 
278  def visit_Return(self, node):
279  self.kwkw.return_ = node.values
280 
281  def visit_Timeout(self, node):
282  self.kwkw.timeout = node.value
283 
284  def visit_Teardown(self, node):
285  self.kwkw.teardown.config(name=node.name, args=node.args,
286  lineno=node.lineno)
287 
288  def visit_KeywordCall(self, node):
289  self.kwkw.body.create_keyword(name=node.keyword, args=node.args,
290  assign=node.assign, lineno=node.lineno)
291 
292  def visit_ReturnStatement(self, node):
293  self.kwkw.body.create_return(node.values, lineno=node.lineno,
294  error=format_error(node.errors))
295 
296  def visit_Continue(self, node):
297  self.kwkw.body.create_continue(lineno=node.lineno,
298  error=format_error(node.errors))
299 
300  def visit_Break(self, node):
301  self.kwkw.body.create_break(lineno=node.lineno,
302  error=format_error(node.errors))
303 
304  def visit_For(self, node):
305  ForBuilder(self.kwkw).build(node)
306 
307  def visit_While(self, node):
308  WhileBuilder(self.kwkw).build(node)
309 
310  def visit_If(self, node):
311  IfBuilder(self.kwkw).build(node)
312 
313  def visit_Try(self, node):
314  TryBuilder(self.kwkw).build(node)
315 
316 
317 class ForBuilder(NodeVisitor):
318 
319  def __init__(self, parent):
320  self.parentparent = parent
321  self.modelmodel = None
322 
323  def build(self, node):
324  error = format_error(self._get_errors_get_errors(node))
325  self.modelmodel = self.parentparent.body.create_for(
326  node.variables, node.flavor, node.values, lineno=node.lineno, error=error
327  )
328  for step in node.body:
329  self.visit(step)
330  return self.modelmodel
331 
332  def _get_errors(self, node):
333  errors = node.header.errors + node.errors
334  if node.end:
335  errors += node.end.errors
336  return errors
337 
338  def visit_KeywordCall(self, node):
339  self.modelmodel.body.create_keyword(name=node.keyword, args=node.args,
340  assign=node.assign, lineno=node.lineno)
341 
342  def visit_TemplateArguments(self, node):
343  self.modelmodel.body.create_keyword(args=node.args, lineno=node.lineno)
344 
345  def visit_For(self, node):
346  ForBuilder(self.modelmodel).build(node)
347 
348  def visit_While(self, node):
349  WhileBuilder(self.modelmodel).build(node)
350 
351  def visit_If(self, node):
352  IfBuilder(self.modelmodel).build(node)
353 
354  def visit_Try(self, node):
355  TryBuilder(self.modelmodel).build(node)
356 
357  def visit_ReturnStatement(self, node):
358  self.modelmodel.body.create_return(node.values, lineno=node.lineno,
359  error=format_error(node.errors))
360 
361  def visit_Continue(self, node):
362  self.modelmodel.body.create_continue(lineno=node.lineno,
363  error=format_error(node.errors))
364 
365  def visit_Break(self, node):
366  self.modelmodel.body.create_break(lineno=node.lineno,
367  error=format_error(node.errors))
368 
369 
370 class IfBuilder(NodeVisitor):
371 
372  def __init__(self, parent):
373  self.parentparent = parent
374  self.modelmodel = None
375 
376  def build(self, node):
377  root = self.parentparent.body.create_if(lineno=node.lineno,
378  error=format_error(self._get_errors_get_errors(node)))
379  assign = node.assign
380  node_type = None
381  while node:
382  node_type = node.type if node.type != 'INLINE IF' else 'IF'
383  self.modelmodel = root.body.create_branch(node_type, node.condition,
384  lineno=node.lineno)
385  for step in node.body:
386  self.visit(step)
387  if assign:
388  for item in self.modelmodel.body:
389  # Having assign when model item doesn't support assign is an error,
390  # but it has been handled already when model was validated.
391  if hasattr(item, 'assign'):
392  item.assign = assign
393  node = node.orelse
394  # Smallish hack to make sure assignment is always run.
395  if assign and node_type != 'ELSE':
396  root.body.create_branch('ELSE').body.create_keyword(
397  assign=assign, name='BuiltIn.Set Variable', args=['${NONE}']
398  )
399  return root
400 
401  def _get_errors(self, node):
402  errors = node.header.errors + node.errors
403  if node.orelse:
404  errors += self._get_errors_get_errors(node.orelse)
405  if node.end:
406  errors += node.end.errors
407  return errors
408 
409  def visit_KeywordCall(self, node):
410  self.modelmodel.body.create_keyword(name=node.keyword, args=node.args,
411  assign=node.assign, lineno=node.lineno)
412 
413  def visit_TemplateArguments(self, node):
414  self.modelmodel.body.create_keyword(args=node.args, lineno=node.lineno)
415 
416  def visit_For(self, node):
417  ForBuilder(self.modelmodel).build(node)
418 
419  def visit_While(self, node):
420  WhileBuilder(self.modelmodel).build(node)
421 
422  def visit_If(self, node):
423  IfBuilder(self.modelmodel).build(node)
424 
425  def visit_Try(self, node):
426  TryBuilder(self.modelmodel).build(node)
427 
428  def visit_ReturnStatement(self, node):
429  self.modelmodel.body.create_return(node.values, lineno=node.lineno,
430  error=format_error(node.errors))
431 
432  def visit_Continue(self, node):
433  self.modelmodel.body.create_continue(lineno=node.lineno,
434  error=format_error(node.errors))
435 
436  def visit_Break(self, node):
437  self.modelmodel.body.create_break(lineno=node.lineno,
438  error=format_error(node.errors))
439 
440 
441 class TryBuilder(NodeVisitor):
442 
443  def __init__(self, parent):
444  self.parentparent = parent
445  self.modelmodel = None
446  self.template_errortemplate_error = None
447 
448  def build(self, node):
449  root = self.parentparent.body.create_try(lineno=node.lineno)
450  errors = self._get_errors_get_errors(node)
451  while node:
452  self.modelmodel = root.body.create_branch(node.type, node.patterns,
453  node.pattern_type, node.variable,
454  lineno=node.lineno)
455  for step in node.body:
456  self.visit(step)
457  node = node.next
458  if self.template_errortemplate_error:
459  errors += (self.template_errortemplate_error,)
460  if errors:
461  root.error = format_error(errors)
462  return root
463 
464  def _get_errors(self, node):
465  errors = node.header.errors + node.errors
466  if node.next:
467  errors += self._get_errors_get_errors(node.next)
468  if node.end:
469  errors += node.end.errors
470  return errors
471 
472  def visit_For(self, node):
473  ForBuilder(self.modelmodel).build(node)
474 
475  def visit_While(self, node):
476  WhileBuilder(self.modelmodel).build(node)
477 
478  def visit_If(self, node):
479  IfBuilder(self.modelmodel).build(node)
480 
481  def visit_Try(self, node):
482  TryBuilder(self.modelmodel).build(node)
483 
484  def visit_ReturnStatement(self, node):
485  self.modelmodel.body.create_return(node.values, lineno=node.lineno,
486  error=format_error(node.errors))
487 
488  def visit_Continue(self, node):
489  self.modelmodel.body.create_continue(lineno=node.lineno,
490  error=format_error(node.errors))
491 
492  def visit_Break(self, node):
493  self.modelmodel.body.create_break(lineno=node.lineno,
494  error=format_error(node.errors))
495 
496  def visit_KeywordCall(self, node):
497  self.modelmodel.body.create_keyword(name=node.keyword, args=node.args,
498  assign=node.assign, lineno=node.lineno)
499 
500  def visit_TemplateArguments(self, node):
501  self.template_errortemplate_error = 'Templates cannot be used with TRY.'
502 
503 
504 class WhileBuilder(NodeVisitor):
505 
506  def __init__(self, parent):
507  self.parentparent = parent
508  self.modelmodel = None
509 
510  def build(self, node):
511  error = format_error(self._get_errors_get_errors(node))
512  self.modelmodel = self.parentparent.body.create_while(
513  node.condition, node.limit, lineno=node.lineno, error=error
514  )
515  for step in node.body:
516  self.visit(step)
517  return self.modelmodel
518 
519  def _get_errors(self, node):
520  errors = node.header.errors + node.errors
521  if node.end:
522  errors += node.end.errors
523  return errors
524 
525  def visit_KeywordCall(self, node):
526  self.modelmodel.body.create_keyword(name=node.keyword, args=node.args,
527  assign=node.assign, lineno=node.lineno)
528 
529  def visit_TemplateArguments(self, node):
530  self.modelmodel.body.create_keyword(args=node.args, lineno=node.lineno)
531 
532  def visit_For(self, node):
533  ForBuilder(self.modelmodel).build(node)
534 
535  def visit_While(self, node):
536  WhileBuilder(self.modelmodel).build(node)
537 
538  def visit_If(self, node):
539  IfBuilder(self.modelmodel).build(node)
540 
541  def visit_Try(self, node):
542  TryBuilder(self.modelmodel).build(node)
543 
544  def visit_ReturnStatement(self, node):
545  self.modelmodel.body.create_return(node.values, lineno=node.lineno,
546  error=format_error(node.errors))
547 
548  def visit_Break(self, node):
549  self.modelmodel.body.create_break(error=format_error(node.errors))
550 
551  def visit_Continue(self, node):
552  self.modelmodel.body.create_continue(error=format_error(node.errors))
553 
554 
555 def format_error(errors):
556  if not errors:
557  return None
558  if len(errors) == 1:
559  return errors[0]
560  return '\n- '.join(('Multiple errors:',) + errors)
561 
562 
564  for tag in node.values:
565  if tag.startswith('-'):
566  LOGGER.warn(
567  f"Error in file '{source}' on line {node.lineno}: "
568  f"Settings tags starting with a hyphen using the '[Tags]' setting "
569  f"is deprecated. In Robot Framework 6.1 this syntax will be used "
570  f"for removing tags. Escape '{tag}' like '\\{tag}' to use the "
571  f"literal value and to avoid this warning."
572  )
def _format_template(self, template, arguments)
def deprecate_tags_starting_with_hyphen(node, source)