Robot Framework Integrated Development Environment (RIDE)
builder.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 import os.path
17 import warnings
18 
19 from robotide.lib.robot.errors import DataError
20 from robotide.lib.robot.output import LOGGER
21 from robotide.lib.robot.parsing import TestData, ResourceFile as ResourceData, TEST_EXTENSIONS
22 from robotide.lib.robot.running.defaults import TestDefaults
23 from robotide.lib.robot.utils import abspath, is_string, normalize, unic
24 from robotide.lib.robot.variables import VariableIterator
25 
26 from .model import ForLoop, Keyword, ResourceFile, TestSuite
27 
28 
29 
37 
38 
50  def __init__(self, include_suites=None, warn_on_skipped='DEPRECATED',
51  extension=None, rpa=None):
52  self.include_suitesinclude_suites = include_suites
53  self.extensionsextensions = self._get_extensions_get_extensions(extension)
54  builder = StepBuilder()
55  self._build_steps_build_steps = builder.build_steps
56  self._build_step_build_step = builder.build_step
57  self.rparpa = rpa
58  self._rpa_not_given_rpa_not_given = rpa is None
59  # TODO: Remove in RF 3.2.
60  if warn_on_skipped != 'DEPRECATED':
61  warnings.warn("Option 'warn_on_skipped' is deprecated and has no "
62  "effect.", DeprecationWarning)
63 
64  def _get_extensions(self, extension):
65  if not extension:
66  return None
67  extensions = set(ext.lower().lstrip('.') for ext in extension.split(':'))
68  if not all(ext in TEST_EXTENSIONS for ext in extensions):
69  raise DataError("Invalid extension to limit parsing '%s'." % extension)
70  return extensions
71 
72 
76  def build(self, *paths):
77  if not paths:
78  raise DataError('One or more source paths required.')
79  if len(paths) == 1:
80  return self._parse_and_build_parse_and_build(paths[0])
81  root = TestSuite()
82  for path in paths:
83  root.suites.append(self._parse_and_build_parse_and_build(path))
84  root.rpa = self.rparpa
85  return root
86 
87  def _parse_and_build(self, path):
88  suite = self._build_suite_build_suite(self._parse_parse(path))
89  suite.remove_empty_suites()
90  return suite
91 
92  def _parse(self, path):
93  try:
94  return TestData(source=abspath(path),
95  include_suites=self.include_suitesinclude_suites,
96  extensions=self.extensionsextensions)
97  except DataError as err:
98  raise DataError("Parsing '%s' failed: %s" % (path, err.message))
99 
100  def _build_suite(self, data, parent_defaults=None):
101  if self._rpa_not_given_rpa_not_given and data.testcase_table.is_started():
102  self._set_execution_mode_set_execution_mode(data)
103  self._check_deprecated_extensions_check_deprecated_extensions(data.source)
104  defaults = TestDefaults(data.setting_table, parent_defaults)
105  suite = TestSuite(name=data.name,
106  source=data.source,
107  doc=unic(data.setting_table.doc),
108  metadata=self._get_metadata_get_metadata(data.setting_table))
109  self._build_setup_build_setup(suite, data.setting_table.suite_setup)
110  self._build_teardown_build_teardown(suite, data.setting_table.suite_teardown)
111  for test_data in data.testcase_table.tests:
112  self._build_test_build_test(suite, test_data, defaults)
113  for child in data.children:
114  suite.suites.append(self._build_suite_build_suite(child, defaults))
115  suite.rpa = self.rparpa
116  ResourceFileBuilder().build(data, target=suite.resource)
117  return suite
118 
119  def _set_execution_mode(self, data):
120  rpa = normalize(data.testcase_table.header[0]) in ('task', 'tasks')
121  if self.rparpa is None:
122  self.rparpa = rpa
123  elif self.rparpa is not rpa:
124  this, that = ('tasks', 'tests') if rpa else ('tests', 'tasks')
125  raise DataError("Conflicting execution modes. File '%s' has %s "
126  "but files parsed earlier have %s. Fix headers "
127  "or use '--rpa' or '--norpa' options to set the "
128  "execution mode explicitly."
129  % (data.source, this, that))
130 
131  def _check_deprecated_extensions(self, source):
132  if os.path.isdir(source):
133  return
134  ext = os.path.splitext(source)[1][1:].lower()
135  if self.extensionsextensions and ext in self.extensionsextensions:
136  return
137  # HTML files cause deprecation warning that cannot be avoided with
138  # --extension at parsing time. No need for double warning.
139  if ext not in ('robot', 'html', 'htm', 'xhtml'):
140  LOGGER.warn("Automatically parsing other than '*.robot' files is "
141  "deprecated. Convert '%s' to '*.robot' format or use "
142  "'--extension' to explicitly configure which files to "
143  "parse." % source)
144 
145  def _get_metadata(self, settings):
146  # Must return as a list to preserve ordering
147  return [(meta.name, meta.value) for meta in settings.metadata]
148 
149  def _build_test(self, suite, data, defaults):
150  values = defaults.get_test_values(data)
151  template = self._get_template_get_template(values.template)
152  test = suite.tests.create(name=data.name,
153  doc=unic(data.doc),
154  tags=values.tags.value,
155  template=template,
156  timeout=self._get_timeout_get_timeout(values.timeout))
157  self._build_setup_build_setup(test, values.setup)
158  self._build_steps_build_steps(test, data, template)
159  self._build_teardown_build_teardown(test, values.teardown)
160 
161  def _get_timeout(self, timeout):
162  return (timeout.value, timeout.message) if timeout else None
163 
164  def _get_template(self, template):
165  return unic(template) if template.is_active() else None
166 
167  def _build_setup(self, parent, data):
168  if data.is_active():
169  self._build_step_build_step(parent, data, kw_type='setup')
170 
171  def _build_teardown(self, parent, data):
172  if data.is_active():
173  self._build_step_build_step(parent, data, kw_type='teardown')
174 
175 
177 
178  def __init__(self):
179  builder = StepBuilder()
180  self._build_steps_build_steps = builder.build_steps
181  self._build_step_build_step = builder.build_step
182 
183  def build(self, path_or_data, target=None):
184  data, source = self._import_resource_if_needed_import_resource_if_needed(path_or_data)
185  if not target:
186  target = ResourceFile(doc=data.setting_table.doc.value, source=source)
187  self._build_imports_build_imports(target, data.setting_table.imports)
188  self._build_variables_build_variables(target, data.variable_table.variables)
189  for kw_data in data.keyword_table.keywords:
190  self._build_keyword_build_keyword(target, kw_data)
191  return target
192 
193  def _import_resource_if_needed(self, path_or_data):
194  if not is_string(path_or_data):
195  return path_or_data, path_or_data.source
196  return ResourceData(path_or_data).populate(), path_or_data
197 
198  def _build_imports(self, target, imports):
199  for data in imports:
200  target.imports.create(type=data.type,
201  name=data.name,
202  args=tuple(data.args),
203  alias=data.alias)
204 
205  def _build_variables(self, target, variables):
206  for data in variables:
207  if data:
208  target.variables.create(name=data.name, value=data.value)
209 
210  def _build_keyword(self, target, data):
211  kw = target.keywords.create(name=data.name,
212  args=tuple(data.args),
213  doc=unic(data.doc),
214  tags=tuple(data.tags),
215  return_=tuple(data.return_),
216  timeout=self._get_timeout_get_timeout(data.timeout))
217  self._build_steps_build_steps(kw, data)
218  if data.teardown.is_active():
219  self._build_step_build_step(kw, data.teardown, kw_type='teardown')
220 
221  def _get_timeout(self, timeout):
222  return (timeout.value, timeout.message) if timeout else None
223 
224 
225 class StepBuilder():
226 
227  def build_steps(self, parent, data, template=None, kw_type='kw'):
228  steps = [self._build_build(step, template, kw_type) for step in data.steps
229  if step and not step.is_comment()]
230  parent.keywords.extend(steps)
231 
232  def build_step(self, parent, data, template=None, kw_type='kw'):
233  if data and not data.is_comment():
234  step = self._build_build(data, template, kw_type)
235  parent.keywords.append(step)
236 
237  def _build(self, data, template=None, kw_type='kw'):
238  if data.is_for_loop():
239  return self._build_for_loop_build_for_loop(data, template)
240  if template:
241  return self._build_templated_step_build_templated_step(data, template)
242  return self._build_normal_step_build_normal_step(data, kw_type)
243 
244  def _build_for_loop(self, data, template):
245  loop = ForLoop(variables=data.vars,
246  values=data.items,
247  flavor=data.flavor)
248  self.build_stepsbuild_steps(loop, data, template)
249  return loop
250 
251  def _build_templated_step(self, data, template):
252  args = data.as_list(include_comment=False)
253  template, args = self._format_template_format_template(template, args)
254  return Keyword(name=template, args=args)
255 
256  def _format_template(self, template, args):
257  iterator = VariableIterator(template, identifiers='$')
258  variables = len(iterator)
259  if not variables or variables != len(args):
260  return template, tuple(args)
261  temp = []
262  for before, variable, after in iterator:
263  temp.extend([before, args.pop(0)])
264  temp.append(after)
265  return ''.join(temp), ()
266 
267  def _build_normal_step(self, data, kw_type):
268  return Keyword(name=data.name,
269  args=tuple(data.args),
270  assign=tuple(data.assign),
271  type=kw_type)
Used when variable does not exist.
Definition: errors.py:67
The parsed resource file object.
Definition: model.py:254
def _build_variables(self, target, variables)
Definition: builder.py:205
def build(self, path_or_data, target=None)
Definition: builder.py:183
def _format_template(self, template, args)
Definition: builder.py:256
def _build_normal_step(self, data, kw_type)
Definition: builder.py:267
def build_step(self, parent, data, template=None, kw_type='kw')
Definition: builder.py:232
def _build_for_loop(self, data, template)
Definition: builder.py:244
def _build(self, data, template=None, kw_type='kw')
Definition: builder.py:237
def build_steps(self, parent, data, template=None, kw_type='kw')
Definition: builder.py:227
def _build_templated_step(self, data, template)
Definition: builder.py:251
Creates executable :class:~robot.running.model.TestSuite objects.
Definition: builder.py:36
def build(self, *paths)
:param paths: Paths to test data files or directories.
Definition: builder.py:76
def _build_test(self, suite, data, defaults)
Definition: builder.py:149
def _build_suite(self, data, parent_defaults=None)
Definition: builder.py:100
def __init__(self, include_suites=None, warn_on_skipped='DEPRECATED', extension=None, rpa=None)
:param include_suites: List of suite names to include.
Definition: builder.py:51
Represents a for loop in test data.
Definition: model.py:68
Represents a single executable keyword.
Definition: model.py:52
Represents a single executable test suite.
Definition: model.py:117
def TestData(source, parent=None, settings=None)
Parses a file or directory to a corresponding model object.
Definition: dataloader.py:127
def normalize(string, ignore=(), caseless=True, spaceless=True)
Normalizes given string according to given spec.
Definition: normalizing.py:30
def abspath(path, case_normalize=False)
Replacement for os.path.abspath with some enhancements and bug fixes.
Definition: robotpath.py:87