Robot Framework Integrated Development Environment (RIDE)
project.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
17 import shutil
18 import tempfile
19 
20 from .basecontroller import WithNamespace, _BaseController
21 from .dataloader import DataLoader
22 from .robotdata import NewTestCaseFile, NewTestDataDirectory
23 from ..context import LOG
24 from ..controller.ctrlcommands import NullObserver, SaveFile
25 from ..publish.messages import RideOpenSuite, RideNewProject, RideFileNameChanged
26 from .. import spec
27 # from ..spec.librarymanager import LibraryManager
28 from ..spec.xmlreaders import SpecInitializer
29 from ..utils import overrides
30 
31 
33 
34  def __init__(self, namespace=None, settings=None, library_manager=None):
35  from .filecontrollers import ResourceFileControllerFactory
36  self._library_manager_library_manager = self._construct_library_manager_construct_library_manager(library_manager, settings)
37  if not self._library_manager_library_manager.is_alive():
38  self._library_manager_library_manager.start()
39  self._name_space_name_space = namespace
40  self._set_namespace_set_namespace_set_namespace(self._name_space_name_space)
41  self._settings_settings = settings
42  self._loader_loader = DataLoader(self._name_space_name_space, settings)
43  self._controller_controller = None
44  self.namename = None
45  self.external_resourcesexternal_resources = []
46  self._resource_file_controller_factory_resource_file_controller_factory = ResourceFileControllerFactory(self._name_space_name_space, self)
47  self._serializer_serializer = Serializer(settings, LOG)
48 
49  @staticmethod
50  def _construct_library_manager(library_manager, settings):
51  return library_manager or \
52  spec.LibraryManager(spec.DATABASE_FILE, SpecInitializer(settings.get('library xml directories', [])[:]))
53 
54  def __del__(self):
55  if self._library_manager_library_manager:
56  self.closeclose()
57 
58  def close(self):
59  self._library_manager_library_manager.stop()
60  self._library_manager_library_manager = None
61 
62  @overrides(WithNamespace)
63  def _set_namespace(self, namespace):
64  namespace.set_library_manager(self._library_manager_library_manager)
65  WithNamespace._set_namespace(self, namespace)
66 
67  @property
68  display_name = property
69 
70  def display_name(self):
71  return 'Project'
72 
73  @property
74  default_dir = property
75 
76  def default_dir(self):
77  return os.path.abspath(self._settings_settings.get('default directory', ''))
78 
79  def update_default_dir(self, path):
80  default_dir = path if os.path.isdir(path) else os.path.dirname(path)
81  self._settings_settings.set('default directory', default_dir)
82  self._name_space_name_space.update_exec_dir_global_var(default_dir)
83  self._name_space_name_space.update_cur_dir_global_var(default_dir)
84 
85  # TODO: in all other controllers data returns a robot data model object.
86  @property
87  data = property
88 
89  def data(self):
90  return self._controller_controller
91 
92  @property
93  suite = property
94 
95  def suite(self):
96  return self._controller_controller.data if self._controller_controller else None
97 
98  @property
99  datafiles = property
100 
101  def datafiles(self):
102  return self._suites_suites() + self.resourcesresourcesresources
103 
104  @property
105  resources = property
106 
107  def resources(self):
108  return self._resource_file_controller_factory_resource_file_controller_factory.resources
109 
110  @property
111  resource_file_controller_factory = property
112 
114  return self._resource_file_controller_factory_resource_file_controller_factory
115 
116  def find_controller_by_longname(self, longname, testname=None):
117  return self._controller_controller.find_controller_by_longname(longname, testname)
118 
119  def new_directory_project(self, path):
120  self._new_project_new_project(NewTestDataDirectory(path))
121 
122  def new_file_project(self, path):
123  self._new_project_new_project(NewTestCaseFile(path))
124 
125  def _new_project(self, datafile):
126  from .filecontrollers import DataController, ResourceFileControllerFactory
127  self.update_default_dirupdate_default_dir(datafile.directory)
128  self._controller_controller = DataController(datafile, self)
129  self._resource_file_controller_factory_resource_file_controller_factory = ResourceFileControllerFactory(self._namespace_namespace, self)
130  RideNewProject(path=datafile.source, datafile=datafile).publish()
131 
132  def new_resource(self, path, parent=None):
133  res = self._namespace_namespace.new_resource(path)
134  resource_controller = self._create_resource_controller_create_resource_controller(res, parent)
135  return resource_controller
136 
137  def load_data(self, path, load_observer=None):
138  load_observer = load_observer or NullObserver()
139  if self._load_initfile_load_initfile(path, load_observer):
140  return
141  if self._load_datafile_load_datafile(path, load_observer):
142  return
143  if self._load_resource_load_resource(path, load_observer):
144  return
145  try:
146  load_observer.error("Given file '%s' is not a valid Robot Framework "
147  "test case or resource file." % path)
148  except AttributeError: # DEBUG
149  # print(f"DEBUG: load_data error not valid datafile: {path}")
150  pass
151 
152  def is_excluded(self, source):
153  return self._settings_settings.excludes.contains(source) if self._settings_settings else False
154 
155  def _load_initfile(self, path, load_observer):
156  if not os.path.splitext(os.path.split(path)[1])[0] == '__init__':
157  return None
158  initfile = self._loader_loader.load_initfile(path, load_observer)
159  if not initfile:
160  return None
161  self._populate_from_datafile_populate_from_datafile(path, initfile, load_observer)
162  return initfile
163 
164  def load_datafile(self, path, load_observer):
165  datafile = self._load_datafile_load_datafile(path, load_observer)
166  if datafile:
167  return datafile
168  # print(f"DEBUG: project Before testing Resource load_datafile path={path}")
169  # Let's see if it is a .robot file valid as .resource
170  if path.endswith((".robot", ".resource")):
171  datafile = self._load_resource_load_resource(path, load_observer)
172  if datafile:
173  return datafile
174  load_observer.error("Invalid data file '%s'." % path)
175  e = UserWarning("Invalid data file")
176  return e
177 
178  def _load_datafile(self, path, load_observer):
179  datafile = self._loader_loader.load_datafile(path, load_observer)
180  if not datafile:
181  return None
182  self._populate_from_datafile_populate_from_datafile(path, datafile, load_observer)
183  return datafile
184 
185  def _populate_from_datafile(self, path, datafile, load_observer):
186  self.__init____init__(self._namespace_namespace, self._settings_settings, library_manager=self._library_manager_library_manager)
187  resources = self._loader_loader.resources_for(datafile, load_observer)
188  self._create_controllers_create_controllers(datafile, resources)
189  RideOpenSuite(path=path, datafile=self._controller_controller).publish()
190  load_observer.finish()
191 
192  def _create_controllers(self, datafile, resources):
193  from .filecontrollers import DataController
194  self.clear_namespace_update_listenersclear_namespace_update_listeners()
195  self._controller_controller = DataController(datafile, self)
196  new_resource_controllers = []
197  for r in resources:
198  self._create_resource_controller_create_resource_controller(r, resource_created_callback=lambda controller:
199  new_resource_controllers.append(controller))
200  for controller in new_resource_controllers:
201  self._inform_resource_created_inform_resource_created(controller)
202 
203  def load_resource(self, path, load_observer):
204  resource = self._load_resource_load_resource(path, load_observer)
205  if resource:
206  return resource
207  load_observer.error("Invalid resource file '%s'." % path)
208 
209  def _load_resource(self, path, load_observer):
210  resource = self._loader_loader.load_resource_file(path, load_observer)
211  if not resource:
212  return None
213  ctrl = self._create_resource_controller_create_resource_controller(resource)
214  load_observer.finish()
215  return ctrl
216 
217  def _create_resource_controller(self, parsed_resource, parent=None,
218  resource_created_callback=None):
219  old = self._resource_file_controller_factory_resource_file_controller_factory.find(parsed_resource)
220  if old:
221  return old
222  controller = self._resource_file_controller_factory_resource_file_controller_factory.create(parsed_resource, parent=parent)
223  self.insert_into_suite_structureinsert_into_suite_structure(controller)
224  resource_created_callback = resource_created_callback or self._inform_resource_created_inform_resource_created
225  resource_created_callback(controller)
226  return controller
227 
228  @staticmethod
229  def _inform_resource_created(controller):
230  controller.notify_opened()
231 
232  def insert_into_suite_structure(self, resource):
233  if self._controller_controller and self._controller_controller.is_inside_top_suite(resource):
234  self._controller_controller.insert_to_test_data_directory(resource)
235  else:
236  self.external_resourcesexternal_resources.append(resource)
237  self._sort_external_resources_sort_external_resources()
238 
240  self.external_resourcesexternal_resources.sort(key=lambda resource: resource.name.lower())
241 
242  def get_all_keywords(self):
243  return self.get_all_keywords_fromget_all_keywords_from(ctrl.datafile for ctrl in self.datafilesdatafilesdatafiles if
244  ctrl.datafile)
245 
246  def all_testcases(self):
247  for df in self._suites_suites():
248  for test in df.tests:
249  yield test
250 
251  def get_files_without_format(self, controller=None):
252  if controller:
253  controller_list = [controller]
254  else:
255  controller_list = self._get_all_dirty_controllers_get_all_dirty_controllers()
256  return [dc for dc in controller_list if dc.dirty and not dc.has_format()]
257 
259  return self.suitesuitesuite.get_dir_path()
260 
261  def is_dirty(self):
262  if self.datadatadata and self.is_datafile_dirtyis_datafile_dirty(self.datadatadata):
263  return True
264  for res in self.resourcesresourcesresources:
265  if res.dirty:
266  return True
267  return False
268 
269  def is_datafile_dirty(self, datafile):
270  if datafile.dirty:
271  return True
272  for df in datafile.children:
273  if self.is_datafile_dirtyis_datafile_dirty(df):
274  return True
275  return False
276 
277  def change_format(self, controller, format):
278  if controller.is_same_format(format):
279  return
280  old_path = controller.filename
281  controller.set_format(format)
282  controller.execute(SaveFile(self._settings_settings.get('reformat', False)))
283  if old_path:
284  self._remove_file_remove_file(old_path)
285  RideFileNameChanged(old_filename=old_path,
286  datafile=controller).publish()
287 
288  def _remove_file(self, path):
289  if path and os.path.isfile(path):
290  os.remove(path)
291 
292  def change_format_recursive(self, controller, format):
293  for datafile in controller.iter_datafiles():
294  self.change_formatchange_format(datafile, format)
295 
296  def remove_datafile(self, controller):
297  if controller is self._controller_controller:
298  self._controller_controller = None
299  else:
300  self._controller_controller.remove_child(controller)
301 
302  def remove_resource(self, controller):
303  self._resource_file_controller_factory_resource_file_controller_factory.remove(controller)
304 
305  def save(self, controller):
306  assert controller is not None
307  self._serializer_serializer.serialize_file(controller)
308 
310  return [controller for controller in self.datafilesdatafilesdatafiles if controller.dirty]
311 
312  def _suites(self):
313  if not self.datadatadata:
314  return []
315  return [df for df in self.datadatadata.iter_datafiles() if not (df in self.resourcesresourcesresources)]
316 
317  def resource_import_modified(self, path, directory):
318  resource = self._namespace_namespace.get_resource(path, directory)
319  if resource:
320  return self._create_resource_controller_create_resource_controller(resource)
321 
323  from .filecontrollers import TestDataDirectoryController
324  for data_file in self.datafilesdatafilesdatafiles:
325  if isinstance(data_file, TestDataDirectoryController):
326  # print(f"DEBUG: Project is_project_changed_from_disk directory is {data_file.directory}")
327  if not os.path.exists(data_file.directory):
328  # print(f"DEBUG: Project is_project_changed_from_disk directory TRUE {data_file.directory}")
329  return True
330  else:
331  if data_file.has_been_modified_on_disk() or \
332  data_file.has_been_removed_from_disk():
333  # print(f"DEBUG: Project is_project_changed_from_disk FILE TRUE {data_file.name}")
334  return True
335  return False
336 
337 
338 class Serializer():
339 
340  def __init__(self, settings, logger):
341  self._settings_settings = settings
342  self._logger_logger = logger
343  self._errors_errors = []
344 
345  def serialize_file(self, controller):
346  try:
347  self._serialize_file_serialize_file(controller)
348  finally:
349  self._log_errors_log_errors()
350 
351  def _serialize_file(self, controller):
352  with Backup(controller):
353  try:
354  controller.datafile.save(**self._get_options_get_options())
355  except Exception as err:
356  self._cache_error_cache_error(controller, err)
357  raise
358 
359  def _get_options(self):
360  return {'line_separator': self._get_line_separator_get_line_separator(),
361  'pipe_separated': self._get_pipe_separated_get_pipe_separated(),
362  'txt_separating_spaces': self._get_separating_spaces_get_separating_spaces()}
363 
365  setting = self._settings_settings.get('line separator', 'native').lower()
366  if setting in ('crlf', 'windows'):
367  return '\r\n'
368  if setting in ('lf', 'unix'):
369  return '\n'
370  return os.linesep
371 
373  return self._settings_settings.get('txt format separator', 'space') == 'pipe'
374 
376  return self._settings_settings.get('txt number of spaces', 4)
377 
378  def _cache_error(self, data, error):
379  self._errors_errors.append("Error in serializing '%s':\n%s"
380  % (data.data.source, str(error)))
381 
382  def _log_errors(self):
383  if self._errors_errors:
384  self._logger_logger.error('\n\n'.join(self._errors_errors))
385  self._errors_errors = []
386 
387 
388 class Backup():
389 
390  def __init__(self, file_controller):
391  self._path_path = file_controller.filename
392  self._file_controller_file_controller = file_controller
393  self._backup_backup = self._get_backup_name_get_backup_name(self._path_path)
394 
395  def _get_backup_name(self, path):
396  if path is None or not os.path.isfile(path):
397  return None
398  self._backup_dir_backup_dir = tempfile.mkdtemp(dir=os.path.dirname(path))
399  return os.path.join(self._backup_dir_backup_dir, os.path.basename(path))
400 
401  def __enter__(self):
402  self._make_backup_make_backup()
403 
404  def _make_backup(self):
405  if self._backup_backup:
406  try:
407  self._move_move(self._path_path, self._backup_backup)
408  except IOError:
409  self._backup_backup = None
410  self._remove_backup_dir_remove_backup_dir()
411 
412  def __exit__(self, *args):
413  if any(args):
414  self._restore_backup_restore_backup()
415  else:
416  self._remove_backup_remove_backup()
417 
418  def _remove_backup(self):
419  if self._backup_backup:
420  os.remove(self._backup_backup)
421  self._remove_backup_dir_remove_backup_dir()
422 
423  def _restore_backup(self):
424  if self._backup_backup:
425  self._move_move(self._backup_backup, self._path_path)
426  self._file_controller_file_controller.refresh_stat()
427  self._remove_backup_dir_remove_backup_dir()
428 
429  def _move(self, from_path, to_path):
430  shutil.move(from_path, to_path)
431 
433  shutil.rmtree(self._backup_dir_backup_dir)
def _move(self, from_path, to_path)
Definition: project.py:429
def _get_backup_name(self, path)
Definition: project.py:395
def __init__(self, file_controller)
Definition: project.py:390
def __init__(self, namespace=None, settings=None, library_manager=None)
Definition: project.py:34
def update_default_dir(self, path)
Definition: project.py:79
def _load_datafile(self, path, load_observer)
Definition: project.py:178
def new_resource(self, path, parent=None)
Definition: project.py:132
def _create_resource_controller(self, parsed_resource, parent=None, resource_created_callback=None)
Definition: project.py:218
def save(self, controller)
Definition: project.py:305
def load_resource(self, path, load_observer)
Definition: project.py:203
def _load_initfile(self, path, load_observer)
Definition: project.py:155
def _set_namespace(self, namespace)
Definition: project.py:63
def _new_project(self, datafile)
Definition: project.py:125
def load_datafile(self, path, load_observer)
Definition: project.py:164
def _inform_resource_created(controller)
Definition: project.py:229
def new_directory_project(self, path)
Definition: project.py:119
def new_file_project(self, path)
Definition: project.py:122
def get_files_without_format(self, controller=None)
Definition: project.py:251
def is_datafile_dirty(self, datafile)
Definition: project.py:269
def insert_into_suite_structure(self, resource)
Definition: project.py:232
def change_format(self, controller, format)
Definition: project.py:277
def _create_controllers(self, datafile, resources)
Definition: project.py:192
def load_data(self, path, load_observer=None)
Definition: project.py:137
def is_excluded(self, source)
Definition: project.py:152
def resource_import_modified(self, path, directory)
Definition: project.py:317
def find_controller_by_longname(self, longname, testname=None)
Definition: project.py:116
def change_format_recursive(self, controller, format)
Definition: project.py:292
def _construct_library_manager(library_manager, settings)
Definition: project.py:50
def remove_datafile(self, controller)
Definition: project.py:296
def remove_resource(self, controller)
Definition: project.py:302
def _load_resource(self, path, load_observer)
Definition: project.py:209
def _populate_from_datafile(self, path, datafile, load_observer)
Definition: project.py:185
def _serialize_file(self, controller)
Definition: project.py:351
def _cache_error(self, data, error)
Definition: project.py:378
def serialize_file(self, controller)
Definition: project.py:345
def __init__(self, settings, logger)
Definition: project.py:340
Sent after test suite or resource file is renamed.
Definition: messages.py:307
Sent when a new project has been created.
Definition: messages.py:277
Sent when a new suite has finished loading.
Definition: messages.py:287
def DataController(data, project, parent=None)
def error(msg, html=False)
Writes the message to the log file using the ERROR level.
Definition: logger.py:130