""" Data model of a test specification. A instance of a test case consists out of * test case information * the steps The numeration of the steps consists out ouf a primary counter and a secondary counter, separated by a dot (e.g. 2.1). Although this approach may look like a grouping, it is not intended to be. It is more a history preservation if the test altered after an official delivery. Features: * adding a step * adding a step above (:py:meth:`~test_specification.TestSequence.add_step_above`) * adding a step below (:py:meth:`~test_specification.TestSequence.add_step_below`) * removing a step (:py:meth:`~test_specification.TestSequence.remove_step`) * renumber the steps (decrease_step_numbers, increase_step_numbers) * :py:func:`~test_specification.TestSequence.decrease_step_numbers` * :py:func:`~test_specification.TestSequence.increase_step_numbers` * Lock-mechanism for the numeration of the steps. * If a test is locked: * the primary counter of a step is "frozen", new steps get numerated using the secondary counter * the behaviour of adding a step is changed * deleting a step is not possible anymore * if a test is unlocked: * re-numeration for all steps should be possible only manually * generating a JSON string out of the data (encode_to_json, serialize) * :py:func:`~test_specification.TestSequence.encode_to_json` * :py:func:`~test_specification.TestSequence.serialize` * creating a instance from a JSON string (decode_to_json) * :py:func:`~test_specification.TestSequence.decode_to_json` Parallel processes: A test can have parallel processes. To mark if a step is has the attribute 'sequence'. The different sequences are numbered, starting at 0. To start a sequence a step should be made and a function should be called. """ import json import copy import logging import os import gettext import unittest import confignator import toolbox logger = logging.getLogger(__name__) logger.setLevel(level=logging.DEBUG) console_hdlr = toolbox.create_console_handler(hdlr_lvl=logging.DEBUG) logger.addHandler(hdlr=console_hdlr) # using gettext for internationalization (i18n) localedir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'locale') translate = gettext.translation('handroll', localedir, fallback=True) _ = translate.gettext def create_step_number(primary_counter: int, secondary_counter: int) -> str: """ Using integers to build a string consisting of primary counter and secondary counter, which are separated by a dot. :param int primary_counter: the primary counter of the step :param int secondary_counter: the secondary counter of the step :return: the step number as a string (<primary_counter>.<secondary_counter> :rtype: str """ assert isinstance(primary_counter, int) assert isinstance(secondary_counter, int) step_number_string = str(primary_counter) + '.' + str(secondary_counter) return step_number_string def parse_step_number(step_number: (str, int, float)): """ Parses the input value into a tuple of primary counter and secondary counter. :param str, int, float step_number: the value which should be a step number :return: a tuple consisting out of the primary counter and secondary counter of a step :rtype: (int, int) """ assert isinstance(step_number, (str, int, float)) step_number_string = str(step_number) separator = step_number_string.find('.') if separator != -1: # found the separator in the string primary_counter = int(step_number_string[:separator]) secondary_counter = int(step_number_string[separator+1:]) else: # did not find the separator in the string primary_counter = int(step_number_string) secondary_counter = 0 return primary_counter, secondary_counter class Step: def __init__(self, test_seq=None, step_num=None, prim_counter=None, seco_counter=None, step=None, logger=logger): self.logger = logger # self._sequence = 0 self._primary_counter = 0 self._secondary_counter = 0 self._step_number = '' self._description = '' self._command_code = '' self._step_comment = '' self._verification_code = '' self._verification_description = '' self._is_active = True self._verified_item = [] self._start_sequence = None self._stop_sequence = None self.test = test_seq # set the step number if step_num is not None: self.primary_counter = parse_step_number(step_num)[0] self.secondary_counter = parse_step_number(step_num)[1] if step_num is None and prim_counter is not None: self.primary_counter = prim_counter if seco_counter is not None: self.secondary_counter = seco_counter else: self.secondary_counter = 0 # if a json is provided, read the step attributes from it if step is not None: self.set_attributes_from_json(step) def __deepcopy__(self, memodict={}): new_step = Step() # new_step.sequence = copy.copy(self.sequence) new_step.primary_counter = copy.copy(self.primary_counter) new_step.secondary_counter = copy.copy(self.secondary_counter) new_step.description = copy.copy(self.description) new_step.command_code = copy.copy(self.command_code) new_step.step_comment = copy.copy(self.step_comment) new_step.verification_code = copy.copy(self.verification_code) new_step.verification_description = copy.copy(self.verification_description) new_step.is_active = copy.copy(self.is_active) new_step.start_sequence = copy.copy(self.start_sequence) new_step.stop_sequence = copy.copy(self.stop_sequence) # new_step._verified_item = copy.copy(self._verified_item) return new_step # @property # def sequence(self): # return self._sequence # # @sequence.setter # def sequence(self, value: int): # assert isinstance(value, int) # self._sequence = value @property def primary_counter(self): return self._primary_counter @primary_counter.setter def primary_counter(self, value: int): assert isinstance(value, int) self._primary_counter = value self._step_number = str(self.primary_counter) + '.' + str(self.secondary_counter) @property def secondary_counter(self): return self._secondary_counter @secondary_counter.setter def secondary_counter(self, value: int): assert isinstance(value, int) self._secondary_counter = value self._step_number = str(self.primary_counter) + '.' + str(self.secondary_counter) # Do not use if a test file should be generated, Output is always '1.0' and not '1_0' # A Point is not supported in a function/method name in python, use 'step_number_test_format' function instead @property def step_number(self): return self._step_number @step_number.setter def step_number(self, value: str): assert isinstance(value, (str, int)) if isinstance(value, str): # verify, that the format of primary.secondary (e.g.: 1.1, 1.2, ...) is fulfilled primary, secondary = parse_step_number(value) self._step_number = value if isinstance(value, int): self._step_number = str(value) @property def description(self): return self._description @description.setter def description(self, value: str): assert isinstance(value, str) self._description = value @property def command_code(self): return self._command_code @command_code.setter def command_code(self, value: str): assert isinstance(value, str) self._command_code = value @property def step_comment(self): return self._step_comment @step_comment.setter def step_comment(self, value: str): assert isinstance(value, str) self._step_comment = value @property def verification_code(self): return self._verification_code @verification_code.setter def verification_code(self, value: str): assert isinstance(value, str) self._verification_code = value @property def verification_description(self): return self._verification_description @verification_description.setter def verification_description(self, value: str): assert isinstance(value, str) self._verification_description = value @property def is_active(self): return self._is_active @is_active.setter def is_active(self, value: bool): assert isinstance(value, bool) self._is_active = value @property def start_sequence(self): return self._start_sequence @start_sequence.setter def start_sequence(self, value: int): assert isinstance(value, int) or value is None self._start_sequence = value @property def stop_sequence(self): return self._stop_sequence @stop_sequence.setter def stop_sequence(self, value: int): assert isinstance(value, int) or value is None self._stop_sequence = value @property def step_number_test_format(self): primary = parse_step_number(self._step_number)[0] secondary = parse_step_number(self._step_number)[1] return str(primary) + '_' + str(secondary) def set_attributes_from_json(self, step): """ Loading the step attributes from a parsed JSON file. :param: dict step: a dictionary parsed from a JSON file containing the information of a step """ try: # self.sequence = step['_sequence'] self.primary_counter = step['_primary_counter'] self.secondary_counter = step['_secondary_counter'] self.description = step['_description'] self.command_code = step['_command_code'] self.step_comment = step['_step_comment'] self.verification_code = step['_verification_code'] self.verification_description = step['_verification_description'] self.is_active = step['_is_active'] except KeyError as error: self.logger.error('KeyError: no {} could be found in the loaded data'.format(error)) # load optional attributes try: self.start_sequence = step['_start_sequence'] self.stop_sequence = step['_stop_sequence'] except KeyError: pass return def increase_primary_counter(self): current_step_number = self.step_number self.primary_counter = self.primary_counter + 1 self.logger.debug('increased step number: {} -> {}'.format(current_step_number, self.step_number)) return def decrease_primary_counter(self): current_step_number = self.step_number self.primary_counter = self.primary_counter - 1 self.logger.debug('decreased step number: {} -> {}'.format(current_step_number, self.step_number)) return def increase_secondary_counter(self): current_step_number = self.step_number self.secondary_counter = self.secondary_counter + 1 own_list_index = self.test.get_step_index(self.step_number) self.logger.debug('increased step number: {} -> {} (index: {})'.format(current_step_number, self.step_number, own_list_index)) return def decrease_secondary_counter(self): current_step_number = self.step_number self.secondary_counter = self.secondary_counter - 1 self.logger.debug('decreased step number: {} -> {}'.format(current_step_number, self.step_number)) return class TestSequence: def __init__(self, sequence=0, json_data=None, logger=logger): """ Creates a new instance of TestSequence. If a JSON object was provided it will be decoded and used otherwise an empty instance is created. For every attribute there is a function to set its value. :param json_data: the JSON object which is used to build the test specification """ self.logger = logger self._sequence = sequence self._name = '' self._description = '' self._spec_version = '' self._primary_counter_locked = False self.steps = [] if json_data is not None: # load from a JSON and then sort the steps by their step number and verify the numbering self.decode_from_json(json_data) self.resort_step_list_by_step_numbers() self.verify_step_list_consistency() def __deepcopy__(self): new_test_seq = TestSequence() new_test_seq.sequence = copy.copy(self.sequence) new_test_seq.name = copy.copy(self.name) new_test_seq.description = copy.copy(self.description) new_test_seq.spec_version = copy.copy(self.spec_version) new_test_seq.primary_counter_locked = copy.copy(self.primary_counter_locked) new_test_seq.steps = copy.deepcopy(self.steps) return new_test_seq @property def primary_counter_locked(self): return self._primary_counter_locked @primary_counter_locked.setter def primary_counter_locked(self, value: bool): assert isinstance(value, bool) self._primary_counter_locked = value @property def name(self): return self._name @name.setter def name(self, value: str): assert isinstance(value, str) self._name = value @property def sequence(self): return self._sequence @sequence.setter def sequence(self, value: int): assert isinstance(value, int) self._sequence = value @property def description(self): return self._description @description.setter def description(self, value: str): assert isinstance(value, str) self._description = value @property def spec_version(self): return self._spec_version @spec_version.setter def spec_version(self, value: str): assert isinstance(value, str) self._spec_version = value def verify_step_list_consistency(self) -> bool: """ Testing the step list for integrity. The step numbers have to increase steadily without gaps. :return: If the step list is consistent, True is returned :rtype: bool """ try: for idx, stp in enumerate(self.steps): if idx == 0: # the first item in the list has to be the step 1.0 assert(stp.primary_counter == 1) assert(stp.secondary_counter == 0) else: last_stp = self.steps[idx - 1] # verify that the step numbers are steadily increasing and are uninterrupted if last_stp.primary_counter == stp.primary_counter: # the secondary counter is greater than the last one assert(stp.secondary_counter > last_stp.secondary_counter) # the difference is 1 assert(stp.secondary_counter - last_stp.secondary_counter == 1) else: # the primary counter is greater than the last one assert(stp.primary_counter > last_stp.primary_counter) # the difference is 1 assert(stp.primary_counter - last_stp.primary_counter == 1) return True except Exception as e: self.logger.exception(e) return False def resort_step_list_by_step_numbers(self): """ The list of the steps is sorted by the step number. """ # sort by secondary counter s = sorted(self.steps, key=lambda step: step.secondary_counter) # sort by primary counter p = sorted(s, key=lambda step: step.primary_counter) # assign the sorted step list self.steps = p def highest_step_number(self) -> str: """ Returns the step number with the highest step number. This is achieved by first sorting the step list, then return the last element in the list :return: step number of the last step :rtype: str """ self.resort_step_list_by_step_numbers() return self.steps[-1].step_number def log_list_of_steps(self): self.logger.debug('--------------------------------------------') self.logger.debug('List of steps:') if len(self.steps) > 0: for index, step in enumerate(self.steps): self.logger.debug('Step {},{} (index: {}, id:{})'.format(step.primary_counter, step.secondary_counter, index, id(step))) else: self.logger.debug('list of steps is empty') self.logger.debug('--------------------------------------------') def get_step_index(self, step_number: (str, int, float)): """ Retrieves, for a given step_number, the index of the step in the step list. Raises LookupError if the step_number has no matching step in the list. :param str, int, float step_number: number of the desired step :return: index of the desired step :rtype: int """ assert isinstance(step_number, (str, int, float)) # find the index within the list of the step index_ref_step = None primary, secondary = parse_step_number(step_number) for ndx, step in enumerate(self.steps): if step.primary_counter == primary: if step.secondary_counter == secondary: index_ref_step = ndx if index_ref_step is None: raise LookupError else: return index_ref_step def get_step(self, step_number: (str, int, float)): """ Retrieves, for a given step_number, the step object :param str, int, float step_number: number of the desired step :return: The step instance. :rtype: data_model.Step """ step_index = self.get_step_index(step_number) step = self.steps[step_index] return step def add_step_above(self, reference_step_position): """ Determines the step number of the step to add. then adds a step above the step provided as argument :param reference_step_position: the step which is the reference for adding a new step above :return: """ # figure out the primary and secondary counter of the new step try: # get index of the reference step index_ref_step = self.get_step_index(reference_step_position) ref_step = self.steps[index_ref_step] index_new_step = index_ref_step try: # get the step before the reference step before_step = self.steps[index_ref_step - 1] except IndexError: before_step = None # determine the primary and secondary counter of the new step if before_step is None or (before_step.secondary_counter == 0 and not self.primary_counter_locked): new_step_primary = ref_step.primary_counter new_step_secondary = 0 else: new_step_primary = before_step.primary_counter new_step_secondary = before_step.secondary_counter + 1 except LookupError: if len(self.steps) == 0: # edge-case: the step list is empty index_new_step = 0 new_step_primary = 1 new_step_secondary = 0 else: self.logger.error('step {} could not be found, thus no step is added below'.format(reference_step_position)) return # create the new step number_new_step = create_step_number(new_step_primary, new_step_secondary) new_step = Step(step_num=number_new_step, test_seq=self) # add the new step to the list - the new step takes the place below the reference step self.steps.insert(index_new_step, new_step) self.logger.debug( 'added Step {}.{} (index: {}, id:{})'.format(new_step.primary_counter, new_step.secondary_counter, index_new_step, id(new_step))) # increase the step numbers of the following steps for step in self.steps[index_new_step + 1:]: if step.primary_counter == new_step.primary_counter and new_step.secondary_counter > 0: step.increase_secondary_counter() elif not self.primary_counter_locked: step.increase_primary_counter() return new_step def add_step_below(self, reference_step_position=None, step_instance=None): # figure out the primary and secondary counter of the new step if reference_step_position is None and len(self.steps) == 0: # edge-case: the step list is empty index_new_step = 0 new_step_primary = 1 new_step_secondary = 0 else: # the step list is not empty, thus find the reference step and the following if reference_step_position is None: # append it index_ref_step = -1 else: try: # get the reference step index_ref_step = self.get_step_index(reference_step_position) except LookupError: # Although the list of steps is not empty, no reference step could be found (for whatever reason). raise Exception ref_step = self.steps[index_ref_step] index_ref_step = self.get_step_index(ref_step.step_number) # if the index was -1, get the actual index index_new_step = index_ref_step + 1 # get the next step if index_ref_step is not None: try: # get the following step of reference step next_step = self.steps[index_ref_step + 1] except IndexError: next_step = None else: next_step = None # determine the primary and secondary counter of the new step if next_step is None or (next_step.secondary_counter == 0 and not self.primary_counter_locked): new_step_primary = ref_step.primary_counter + 1 new_step_secondary = 0 else: new_step_primary = ref_step.primary_counter new_step_secondary = ref_step.secondary_counter + 1 # create the new step number_new_step = create_step_number(new_step_primary, new_step_secondary) if step_instance is None: # make a new Step new_step = Step(step_num=number_new_step, test_seq=self) else: # use the provided Step assert isinstance(step_instance, Step) new_step = step_instance new_step.primary_counter = new_step_primary new_step.secondary_counter = new_step_secondary # add the new step to the list - the new step takes the place below the reference step self.steps.insert(index_new_step, new_step) self.logger.debug('added Step {}.{} (index: {}, id:{})'.format(new_step.primary_counter, new_step.secondary_counter, index_new_step, id(new_step))) # increase the step numbers of the following steps for step in self.steps[index_new_step + 1:]: if step.primary_counter == new_step.primary_counter and new_step.secondary_counter > 0: step.increase_secondary_counter() elif not self.primary_counter_locked: step.increase_primary_counter() return new_step def remove_step(self, step_number: (str, int, float)): """ Deletes a step out of the dictionary for steps. If the step is found, decrease all the following step numbers, then delete the step. :param str, int, float step_number: The step which will be deleted """ try: # get index within the step list of the step index_of_step = self.get_step_index(step_number) except LookupError: self.logger.error('step {} could not be found, thus no step is added below'.format(step_number)) return # decrease step numbers if step has following steps self.decrease_step_numbers(reference_step_number=step_number) # delete the step in the list self.steps.pop(index_of_step) self.logger.debug('deleted Step {} (index: {})'.format(step_number, index_of_step)) return def move_step(self, step_to_move_index: int, desired_position_index: int): """ Moving a step within the data model. A step can be moved upward or downward. Cases: * step_to_move < desired_position * step_to_move == desired_position * do nothing * step_to_move > desired_position :param int step_to_move_index: index in the list of the step which should be moved :param int desired_position_index: index in the list of the moved step after the move """ assert isinstance(step_to_move_index, int) assert isinstance(desired_position_index, int) if desired_position_index == -1: # prevent unexpected behavior if the desired position is before the first item (which has index 0) desired_position_index = 0 self.logger.debug('list of steps before moving:') self.log_list_of_steps() if step_to_move_index < desired_position_index: # move the step downwards # get the step, which should be moved step_to_move = self.steps[step_to_move_index] assert isinstance(step_to_move, Step) # decrease the step counters, since the step gets removed self.decrease_step_numbers(reference_step_index=step_to_move_index) # delete the step from the list logger.debug('delete the step which is moved from the list') self.steps.pop(step_to_move_index) # figure out the which step number to reassign to the step (it was moved, thus it gets a new step number) stored_step_number = step_to_move.step_number if desired_position_index > 0: try: step_before = self.steps[desired_position_index-1] except IndexError: step_before = self.steps[-1] assert isinstance(step_before, Step) if self.primary_counter_locked: step_to_move.primary_counter = step_before.primary_counter step_to_move.secondary_counter = step_before.secondary_counter + 1 else: step_to_move.primary_counter = step_before.primary_counter + 1 step_to_move.secondary_counter = 0 else: # it is moved to the top position step_to_move.primary_counter = 1 step_to_move.secondary_counter = 0 logger.debug('changed step number: {} -> {}'.format(stored_step_number, step_to_move.step_number)) # insert it again at the desired position (-1, because the indices got decreased when the step was deleted) logger.debug('insert the moved step again') self.steps.insert(desired_position_index, step_to_move) # increase the step numbers, since a step was added self.increase_step_numbers(reference_step_index=desired_position_index-1) elif step_to_move_index == desired_position_index: pass elif step_to_move_index > desired_position_index: # move the step upwards # get the step, which should be moved step_to_move = self.steps[step_to_move_index] assert isinstance(step_to_move, Step) # decrease the step counters, since the step is going to be removed self.decrease_step_numbers(reference_step_index=step_to_move_index) # delete the step from the list logger.debug('delete the step which is moved from the list') self.steps.pop(step_to_move_index) # figure out the which step number to reassign to the step (it was moved, thus it gets a new step number) stored_step_number = step_to_move.step_number if desired_position_index > 0: try: step_before = self.steps[desired_position_index - 1] except IndexError: step_before = self.steps[-1] assert isinstance(step_before, Step) if self.primary_counter_locked: step_to_move.primary_counter = step_before.primary_counter step_to_move.secondary_counter = step_before.secondary_counter + 1 else: step_to_move.primary_counter = step_before.primary_counter + 1 step_to_move.secondary_counter = 0 else: # it is moved to the top position step_to_move.primary_counter = 1 step_to_move.secondary_counter = 0 logger.debug('changed step number: {} -> {}'.format(stored_step_number, step_to_move.step_number)) # insert it again at the desired position logger.debug('insert the moved step again') self.steps.insert(desired_position_index, step_to_move) # increase the step numbers, since a step was added self.increase_step_numbers(reference_step_index=desired_position_index) self.logger.debug('list of steps after moving:') self.log_list_of_steps() self.verify_step_list_consistency() def renumber_steps(self): """ Renumbering all steps. The steps are numerated by only using the primary counter. All secondary counters will be set to 0. """ for index, step in enumerate(self.steps): if index == 0: step.primary_counter = 1 step.secondary_counter = 0 else: last_step = self.steps[index-1] step.primary_counter = last_step.primary_counter + 1 step.secondary_counter = 0 return def decrease_step_numbers(self, reference_step_number: (str, int, float) = '', reference_step_index: int = None): """ Decreases the step numbers of all steps following the reference step, excluding it. This function is used to reassign the step numbers after * a step was deleted from the data model * a step was moved within the data model :param (str, int, float) reference_step_number: the number of the step to start the decreasing (EXCLUDING) :param int reference_step_index: the index within the step list of the reference step """ # get the index if reference_step_index is None: try: index_ref_step = self.get_step_index(reference_step_number) except LookupError: index_ref_step = None self.logger.error('could not find the reference step, thus no step numbers where increased') except ValueError: index_ref_step = None self.logger.error('{} is not a valid step number'.format(reference_step_number)) else: index_ref_step = reference_step_index # get the step try: ref_step = self.steps[index_ref_step] assert isinstance(ref_step, Step) except IndexError or TypeError: ref_step = None self.logger.error('could not find the reference step, thus no step numbers where increased') # decrease the step numbers of the following steps for step in self.steps[index_ref_step + 1:]: if ref_step.secondary_counter == 0: # decrease all following primary counter step.decrease_primary_counter() else: # decrease all following secondary counter for steps, # which have the same primary counter as the reference step if step.primary_counter == ref_step.primary_counter: step.decrease_secondary_counter() return def increase_step_numbers(self, reference_step_number: (str, int, float) = '', reference_step_index: int = None): """ Increases the step numbers of all steps following the reference step, excluding it. This function is used to reassign the step numbers after * a step was added to the data model * a step was moved within the data model :param (str, int, float) reference_step_number: the number of the step to start the increasing (EXCLUDING) :param int reference_step_index: the index within the step list of the reference step """ # get the index if reference_step_index is None: try: index_ref_step = self.get_step_index(reference_step_number) except LookupError: index_ref_step = None self.logger.error('could not find the reference step, thus no step numbers where increased') except ValueError: index_ref_step = None self.logger.error('{} is not a valid step number'.format(reference_step_number)) else: index_ref_step = reference_step_index # get the step try: ref_step = self.steps[index_ref_step] assert isinstance(ref_step, Step) except IndexError or TypeError: ref_step = None self.logger.error('could not find the reference step, thus no step numbers where increased') # increase the step numbers of the following steps if ref_step is not None: last_step = ref_step for index, step in enumerate(self.steps[index_ref_step + 1:]): if step.primary_counter == last_step.primary_counter: if step.secondary_counter != 0 and step.primary_counter == ref_step.primary_counter: step.increase_secondary_counter() if step.secondary_counter == 0: step.increase_primary_counter() elif step.secondary_counter != 0: step.increase_primary_counter() last_step = step return def verified_items(self): """ Returns a list of all verified items. The verified items are stored in each step. """ # ToDo return def encode_to_json(self, *args): """ Makes out of the TestSequence a JSON object. """ json_string = None json_string = json.dumps(self, default=self.serialize) return json_string def decode_from_json(self, json_data: dict): """ Create a TestSequence instance from a JSON object. """ try: self.name = json_data['_name'] self.description = json_data['_description'] self.spec_version = json_data['_spec_version'] self.sequence = json_data['_sequence'] except KeyError as keyerror: self.logger.error('KeyError: no {} could be found in the loaded data'.format(keyerror)) self.steps = [] try: for index, step in enumerate(json_data['steps']): self.steps.append(Step(test_seq=self, step=step)) except KeyError as keyerror: self.logger.error('KeyError: no {} could be found in the loaded data'.format(keyerror)) return @staticmethod def serialize(obj): """ JSON serializer for objects which are not serializable by default. Uses the overwritten __deepcopy__ methods of the classes, to make copies. Then takes the dictionarys of them and deletes the objects, which can not be serialized. """ if isinstance(obj, TestSequence): obj_copy = obj.__deepcopy__() obj_dict = obj_copy.__dict__ del obj_dict['logger'] return obj_dict if isinstance(obj, Step): obj_copy = obj.__deepcopy__() obj_dict = obj_copy.__dict__ del obj_dict['test'] del obj_dict['logger'] return obj_dict class TestSpecification: def __init__(self, json_data=None, logger=logger): self.logger = logger self._name = '' self._description = '' self._spec_version = '' self._iasw_version = '' self._primary_counter_locked = False self._precon_name = '' self._precon_code = '' self._precon_descr = '' self._postcon_name = '' self._postcon_code = '' self._postcon_descr = '' self._comment = '' self.sequences = [] if json_data is not None: # load from a JSON and then sort the steps by their step number and verify the numbering self.decode_from_json(json_data) def __deepcopy__(self): new_testspec = TestSpecification() new_testspec.sequences = copy.copy(self.sequences) new_testspec.name = copy.copy(self.name) new_testspec.description = copy.copy(self.description) new_testspec.spec_version = copy.copy(self.spec_version) new_testspec.iasw_version = copy.copy(self.iasw_version) new_testspec.primary_counter_locked = copy.copy(self.primary_counter_locked) new_testspec.precon_name = copy.copy(self.precon_name) new_testspec.precon_code = copy.copy(self.precon_code) new_testspec.precon_descr = copy.copy(self.precon_descr) new_testspec.postcon_name = copy.copy(self.postcon_name) new_testspec.postcon_code = copy.copy(self.postcon_code) new_testspec.postcon_descr = copy.copy(self.postcon_descr) new_testspec.comment = copy.copy(self.comment) return new_testspec @property def primary_counter_locked(self): return self._primary_counter_locked @primary_counter_locked.setter def primary_counter_locked(self, value: bool): assert isinstance(value, bool) self._primary_counter_locked = value # set the primary_counter_locked for all sequences for seq in self.sequences: assert isinstance(seq, TestSequence) seq.primary_counter_locked = value @property def name(self): return self._name @name.setter def name(self, value: str): assert isinstance(value, str) self._name = value @property def description(self): return self._description @description.setter def description(self, value: str): assert isinstance(value, str) self._description = value @property def spec_version(self): return self._spec_version @spec_version.setter def spec_version(self, value: str): assert isinstance(value, str) self._spec_version = value @property def iasw_version(self): return self._iasw_version @iasw_version.setter def iasw_version(self, value: str): assert isinstance(value, str) self._iasw_version = value @property def precon_name(self): return self._precon_name @precon_name.setter def precon_name(self, value: str): assert isinstance(value, str) self._precon_name = value @property def precon_code(self): return self._precon_code @precon_code.setter def precon_code(self, value: str): assert isinstance(value, str) self._precon_code = value @property def precon_descr(self): return self._precon_descr @precon_descr.setter def precon_descr(self, value: str): assert isinstance(value, str) self._precon_descr = value @property def postcon_name(self): return self._postcon_name @postcon_name.setter def postcon_name(self, value: str): assert isinstance(value, str) self._postcon_name = value @property def postcon_code(self): return self._postcon_code @postcon_code.setter def postcon_code(self, value: str): assert isinstance(value, str) self._postcon_code = value @property def postcon_descr(self): return self._postcon_descr @postcon_descr.setter def postcon_descr(self, value: str): assert isinstance(value, str) self._postcon_descr = value @property def comment(self): return self._comment @comment.setter def comment(self, value: str): assert isinstance(value, str) self._comment = value def encode_to_json(self, *args): """ Makes out of the TestSequence a JSON object. """ json_string = None json_string = json.dumps(self, default=self.serialize) return json_string def decode_from_json(self, json_data: dict): """ Create a TestSpecification instance from a JSON object. """ try: self.name = json_data['_name'] self.description = json_data['_description'] self.spec_version = json_data['_spec_version'] self.iasw_version = json_data['_iasw_version'] self.primary_counter_locked = json_data['_primary_counter_locked'] self.precon_name = json_data['_precon_name'] self.precon_code = json_data['_precon_code'] self.precon_descr = json_data['_precon_descr'] self.postcon_name = json_data['_postcon_name'] self.postcon_code = json_data['_postcon_code'] self.postcon_descr = json_data['_postcon_descr'] self.comment = json_data['_comment'] except KeyError as keyerror: self.logger.error('KeyError: no {} could be found in the loaded data'.format(keyerror)) self.sequences = [] try: for index, seq in enumerate(json_data['sequences']): self.sequences.append(TestSequence(json_data=seq, logger=logger)) except KeyError as keyerror: self.logger.error('KeyError: no {} could be found in the loaded data'.format(keyerror)) return @staticmethod def serialize(obj): """ JSON serializer for objects which are not serializable by default. Uses the overwritten __deepcopy__ methods of the classes, to make copies. Then takes the dictionarys of them and deletes the objects, which can not be serialized. """ if isinstance(obj, TestSpecification): obj_copy = obj.__deepcopy__() obj_dict = obj_copy.__dict__ del obj_dict['logger'] return obj_dict if isinstance(obj, TestSequence): obj_copy = obj.__deepcopy__() obj_dict = obj_copy.__dict__ del obj_dict['logger'] return obj_dict if isinstance(obj, Step): obj_copy = obj.__deepcopy__() obj_dict = obj_copy.__dict__ del obj_dict['test'] del obj_dict['logger'] return obj_dict def add_sequence(self): # find out the sequence number for the new one number_of_sequence = len(self.sequences) # append it to the list self.sequences.append(TestSequence(sequence=number_of_sequence, logger=logger)) return number_of_sequence def get_sequence(self, sequence_number): for testseq in self.sequences: if testseq.sequence == sequence_number: return testseq # @unittest.skip("demonstrating skipping") class TestParseStepNumber(unittest.TestCase): """ Test the function parse_step_number. """ def test_datatype_str(self): # valid input self.assertEqual(parse_step_number('1'), (1, 0)) self.assertEqual(parse_step_number('1.0'), (1, 0)) self.assertEqual(parse_step_number('1.1'), (1, 1)) self.assertEqual(parse_step_number('1.2'), (1, 2)) # invalid input self.assertRaises(ValueError, parse_step_number, 'a') self.assertRaises(ValueError, parse_step_number, 'a.a') self.assertRaises(ValueError, parse_step_number, '1a.1') def test_datatype_int(self): self.assertEqual(parse_step_number(1), (1, 0)) self.assertEqual(parse_step_number(2), (2, 0)) def test_datatype_float(self): self.assertEqual(parse_step_number(1.0), (1, 0)) self.assertEqual(parse_step_number(1.1), (1, 1)) self.assertEqual(parse_step_number(1.2), (1, 2)) def test_datatype_list(self): self.assertRaises(AssertionError, parse_step_number, []) # @unittest.skip("demonstrating skipping") class TestResortStepListByStepNumbers(unittest.TestCase): """ Test the function resort_step_list_by_step_numbers. A method to verify the list consistency is implemented. """ @classmethod def setUpClass(self): logger.debug('create a test specification and create step objects (not assigned to the test specification yet)') self.tstspc = TestSequence() self.step_1 = Step(step_num=1.0) self.step_1_1 = Step(step_num=1.1) self.step_1_2 = Step(step_num=1.2) self.step_2 = Step(step_num=2.0) self.step_3 = Step(step_num=3.0) self.step_4 = Step(step_num=4.0) self.step_4_1 = Step(step_num=4.1) self.step_5 = Step(step_num=5.0) self.step_6 = Step(step_num=6.0) self.step_6_1 = Step(step_num=6.1) self.step_6_2 = Step(step_num=6.2) self.step_6_3 = Step(step_num=6.3) self.step_7 = Step(step_num=7.0) def test_list_needs_sorting(self): logger.info('Running: {}'.format(self._testMethodName)) self.tstspc.steps = [ self.step_1, self.step_1_1, self.step_2, self.step_1_2, self.step_3, self.step_4, self.step_4_1, self.step_5, self.step_7, self.step_6, self.step_6_1, self.step_6_3, self.step_6_2, ] self.tstspc.log_list_of_steps() logger.debug('sorting the list') self.tstspc.resort_step_list_by_step_numbers() self.tstspc.log_list_of_steps() self.assertTrue(self.tstspc.verify_step_list_consistency()) def test_nothing_to_sort(self): logger.info('Running: {}'.format(self._testMethodName)) self.tstspc.steps = [ self.step_1, self.step_1_1, self.step_1_2, self.step_2, self.step_3, self.step_4, self.step_4_1, self.step_5, self.step_6, self.step_6_1, self.step_6_2, self.step_6_3, self.step_7 ] self.tstspc.log_list_of_steps() logger.debug('sorting the list') self.tstspc.resort_step_list_by_step_numbers() self.tstspc.log_list_of_steps() self.assertTrue(self.tstspc.verify_step_list_consistency()) # @unittest.skip("demonstrating skipping") class TestAddStepBelow(unittest.TestCase): """ Test if the function add_step_below does what it should. """ def setUp(self) -> None: logger.info('setUp: {}'.format(self._testMethodName)) logger.debug('create a test specification and add three steps') self.tstspc = TestSequence() logger.debug('Python object ID of the test specification: {}'.format(id(self.tstspc))) self.tstspc.log_list_of_steps() # create a new step and append it to the steps list number_new_step = create_step_number(1, 0) step_1 = Step(step_num=number_new_step, test_seq=self.tstspc) self.tstspc.steps.append(step_1) self.stp1 = id(step_1) # create a new step and append it to the steps list number_new_step = create_step_number(2, 0) step_2 = Step(step_num=number_new_step, test_seq=self.tstspc) self.tstspc.steps.append(step_2) self.stp2 = id(step_2) # create a new step and append it to the steps list number_new_step = create_step_number(3, 0) step_3 = Step(step_num=number_new_step, test_seq=self.tstspc) self.tstspc.steps.append(step_3) self.stp3 = id(step_3) self.tstspc.log_list_of_steps() def tearDown(self) -> None: logger.info('tearDown: after running test method: {}'.format(self._testMethodName)) # save the test specification as JSON file json_file = os.path.join(confignator.get_option('logging', 'log-dir'), 'tst/data_model/' + self._testMethodName + '.json') with open(json_file, 'w') as fileobject: data = self.tstspc.encode_to_json() fileobject.write(data) logger.debug('Test specification was dumped as JSON: {}'.format(json_file)) def test_add_step_below_unlocked(self): """ The test specification is unlocked. add a step below step 2, the new step becomes (the new) step 3, the step which was number 3 till now, becomes step 4 """ logger.info('Running {}'.format(self._testMethodName)) self.tstspc.primary_counter_locked = False # verify the numeration of the steps before adding a new step self.assertEqual(self.tstspc.steps[0].step_number, '1.0') self.assertEqual(self.tstspc.steps[1].step_number, '2.0') self.assertEqual(self.tstspc.steps[2].step_number, '3.0') # add the new step logger.info('going to add a step below Step 2.0') new_step = self.tstspc.add_step_below(2.0) nw_stp = id(new_step) self.tstspc.log_list_of_steps() # verify the numeration of the steps self.assertEqual(self.tstspc.steps[0].step_number, '1.0') self.assertEqual(self.tstspc.steps[1].step_number, '2.0') self.assertEqual(self.tstspc.steps[2].step_number, '3.0') self.assertEqual(self.tstspc.steps[3].step_number, '4.0') # verify the order of the steps, by comparing the IDs of the Python objects self.assertEqual(id(self.tstspc.steps[0]), self.stp1) self.assertEqual(id(self.tstspc.steps[1]), self.stp2) self.assertEqual(id(self.tstspc.steps[2]), nw_stp) self.assertEqual(id(self.tstspc.steps[3]), self.stp3) def test_add_step_below_locked(self): """ The test specification is locked. add a step below step 2, the new step becomes (the new) step 3, the step which was number 3 till now, becomes step 4 """ logger.info('Running {}'.format(self._testMethodName)) self.tstspc.primary_counter_locked = True # verify the numeration of the steps before adding a new step self.assertEqual(self.tstspc.steps[0].step_number, '1.0') self.assertEqual(self.tstspc.steps[1].step_number, '2.0') self.assertEqual(self.tstspc.steps[2].step_number, '3.0') # add the new step logger.info('going to add a step below Step 2.0') new_step = self.tstspc.add_step_below(2.0) nw_stp = id(new_step) self.tstspc.log_list_of_steps() # verify the numeration of the steps self.assertEqual(self.tstspc.steps[0].step_number, '1.0') self.assertEqual(self.tstspc.steps[1].step_number, '2.0') self.assertEqual(self.tstspc.steps[2].step_number, '2.1') self.assertEqual(self.tstspc.steps[3].step_number, '3.0') # verify the order of the steps, by comparing the IDs of the Python objects self.assertEqual(id(self.tstspc.steps[0]), self.stp1) self.assertEqual(id(self.tstspc.steps[1]), self.stp2) self.assertEqual(id(self.tstspc.steps[2]), nw_stp) self.assertEqual(id(self.tstspc.steps[3]), self.stp3) # @unittest.skip("demonstrating skipping") class TestAddStepAbove(unittest.TestCase): """ Test if the function add_step_above does what it should. """ def setUp(self) -> None: logger.info('setUp: {}'.format(self._testMethodName)) logger.debug('create a test specification and add three steps') self.tstspc = TestSequence() logger.debug('Python object ID of the test specification: {}'.format(id(self.tstspc))) self.tstspc.log_list_of_steps() # create a new step and append it to the steps list number_new_step = create_step_number(1, 0) step_1 = Step(step_num=number_new_step, test_seq=self.tstspc) self.tstspc.steps.append(step_1) self.stp1 = id(step_1) # create a new step and append it to the steps list number_new_step = create_step_number(2, 0) step_2 = Step(step_num=number_new_step, test_seq=self.tstspc) self.tstspc.steps.append(step_2) self.stp2 = id(step_2) # create a new step and append it to the steps list number_new_step = create_step_number(3, 0) step_3 = Step(step_num=number_new_step, test_seq=self.tstspc) self.tstspc.steps.append(step_3) self.stp3 = id(step_3) self.tstspc.log_list_of_steps() def tearDown(self) -> None: logger.info('tear down after running test method: {}'.format(self._testMethodName)) # save the test specification as JSON file json_file = os.path.join(confignator.get_option('logging', 'log-dir'), 'tst/data_model/' + self._testMethodName + '.json') with open(json_file, 'w') as fileobject: data = self.tstspc.encode_to_json() fileobject.write(data) logger.debug('Test specification was dumped as JSON: {}'.format(json_file)) def test_add_step_above_unlocked(self): """ The test specification is unlocked. add a step above step 2, the new step becomes (the new) step 2, the step which was number 2 till now, becomes step 3 """ logger.info('Running {}'.format(self._testMethodName)) self.tstspc.primary_counter_locked = False # verify the numeration of the steps before adding a new step self.assertEqual(self.tstspc.steps[0].step_number, '1.0') self.assertEqual(self.tstspc.steps[1].step_number, '2.0') self.assertEqual(self.tstspc.steps[2].step_number, '3.0') # add the new step logger.info('going to add a step above Step 2.0') new_step = self.tstspc.add_step_above(2.0) nw_stp = id(new_step) self.tstspc.log_list_of_steps() # verify the numeration of the steps self.assertEqual(self.tstspc.steps[0].step_number, '1.0') self.assertEqual(self.tstspc.steps[1].step_number, '2.0') self.assertEqual(self.tstspc.steps[2].step_number, '3.0') self.assertEqual(self.tstspc.steps[3].step_number, '4.0') # verify the order of the steps, by comparing the IDs of the Python objects self.assertEqual(id(self.tstspc.steps[0]), self.stp1) self.assertEqual(id(self.tstspc.steps[1]), nw_stp) self.assertEqual(id(self.tstspc.steps[2]), self.stp2) self.assertEqual(id(self.tstspc.steps[3]), self.stp3) def test_add_step_above_locked(self): """ The test specification is locked. add a step above step 2, the new step becomes (the new) step 1.1, the step which was number 2 till now, stays step 2 """ logger.info('Running {}'.format(self._testMethodName)) self.tstspc.primary_counter_locked = True # verify the numeration of the steps before adding a new step self.assertEqual(self.tstspc.steps[0].step_number, '1.0') self.assertEqual(self.tstspc.steps[1].step_number, '2.0') self.assertEqual(self.tstspc.steps[2].step_number, '3.0') # add the new step logger.info('going to add a step above Step 2.0') new_step = self.tstspc.add_step_above(2.0) nw_stp = id(new_step) self.tstspc.log_list_of_steps() # verify the numeration of the steps self.assertEqual(self.tstspc.steps[0].step_number, '1.0') self.assertEqual(self.tstspc.steps[1].step_number, '1.1') self.assertEqual(self.tstspc.steps[2].step_number, '2.0') self.assertEqual(self.tstspc.steps[3].step_number, '3.0') # verify the order of the steps, by comparing the IDs of the Python objects self.assertEqual(id(self.tstspc.steps[0]), self.stp1) self.assertEqual(id(self.tstspc.steps[1]), nw_stp) self.assertEqual(id(self.tstspc.steps[2]), self.stp2) self.assertEqual(id(self.tstspc.steps[3]), self.stp3) # @unittest.skip("demonstrating skipping") class TestMoveStep(unittest.TestCase): """ Test if moving of a step upwards or downwards works """ def setUp(self) -> None: logger.info('setUp: {}'.format(self._testMethodName)) logger.debug('create a test specification and add steps') self.tstspc = TestSequence() logger.debug('Python object ID of the test specification: {}'.format(id(self.tstspc))) self.tstspc.log_list_of_steps() # create a new step and append it to the steps list number_new_step = create_step_number(1, 0) step_1 = Step(step_num=number_new_step, test_seq=self.tstspc) self.tstspc.steps.append(step_1) self.stp1 = id(step_1) # create a new step and append it to the steps list number_new_step = create_step_number(2, 0) step_2 = Step(step_num=number_new_step, test_seq=self.tstspc) self.tstspc.steps.append(step_2) self.stp2 = id(step_2) # create a new step and append it to the steps list number_new_step = create_step_number(3, 0) step_3 = Step(step_num=number_new_step, test_seq=self.tstspc) self.tstspc.steps.append(step_3) self.stp3 = id(step_3) # create a new step and append it to the steps list number_new_step = create_step_number(3, 1) step_3_1 = Step(step_num=number_new_step, test_seq=self.tstspc) self.tstspc.steps.append(step_3_1) self.stp3_1 = id(step_3_1) # create a new step and append it to the steps list number_new_step = create_step_number(3, 2) step_3_2 = Step(step_num=number_new_step, test_seq=self.tstspc) self.tstspc.steps.append(step_3_2) self.stp3_2 = id(step_3_2) # create a new step and append it to the steps list number_new_step = create_step_number(3, 3) step_3_3 = Step(step_num=number_new_step, test_seq=self.tstspc) self.tstspc.steps.append(step_3_3) self.stp3_3 = id(step_3_3) # create a new step and append it to the steps list number_new_step = create_step_number(3, 4) step_3_4 = Step(step_num=number_new_step, test_seq=self.tstspc) self.tstspc.steps.append(step_3_4) self.stp3_4 = id(step_3_4) # create a new step and append it to the steps list number_new_step = create_step_number(3, 5) step_3_5 = Step(step_num=number_new_step, test_seq=self.tstspc) self.tstspc.steps.append(step_3_5) self.stp3_5 = id(step_3_5) # create a new step and append it to the steps list number_new_step = create_step_number(4, 0) step_4 = Step(step_num=number_new_step, test_seq=self.tstspc) self.tstspc.steps.append(step_4) self.stp4 = id(step_4) def tearDown(self) -> None: logger.info('tearDown: after running test method: {}'.format(self._testMethodName)) # save the test specification as JSON file json_file = os.path.join(confignator.get_option('logging', 'log-dir'), 'tst/data_model/' + self._testMethodName + '.json') with open(json_file, 'w') as fileobject: data = self.tstspc.encode_to_json() fileobject.write(data) logger.debug('Test specification was dumped as JSON: {}'.format(json_file)) def test_move_step_to_top_unlocked(self): """ It should not matter if the test is unlocked, if moving to the top. Move step 3.2 to the top. """ logger.info('Running {}'.format(self._testMethodName)) self.tstspc.primary_counter_locked = False # verify the numeration of the steps before adding a new step self.assertEqual(self.tstspc.steps[0].step_number, '1.0') self.assertEqual(self.tstspc.steps[1].step_number, '2.0') self.assertEqual(self.tstspc.steps[2].step_number, '3.0') self.assertEqual(self.tstspc.steps[3].step_number, '3.1') self.assertEqual(self.tstspc.steps[4].step_number, '3.2') self.assertEqual(self.tstspc.steps[5].step_number, '3.3') self.assertEqual(self.tstspc.steps[6].step_number, '3.4') self.assertEqual(self.tstspc.steps[7].step_number, '3.5') self.assertEqual(self.tstspc.steps[8].step_number, '4.0') logger.info('moving step 3.2 to the top') step_to_move_index = self.tstspc.get_step_index('3.2') self.tstspc.move_step(step_to_move_index, 0) # verify the numeration of the steps self.assertEqual(self.tstspc.steps[0].step_number, '1.0') self.assertEqual(self.tstspc.steps[1].step_number, '2.0') self.assertEqual(self.tstspc.steps[2].step_number, '3.0') self.assertEqual(self.tstspc.steps[3].step_number, '4.0') self.assertEqual(self.tstspc.steps[4].step_number, '4.1') self.assertEqual(self.tstspc.steps[5].step_number, '4.2') self.assertEqual(self.tstspc.steps[6].step_number, '4.3') self.assertEqual(self.tstspc.steps[7].step_number, '4.4') self.assertEqual(self.tstspc.steps[8].step_number, '5.0') # verify the order of the steps, by comparing the IDs of the Python objects self.assertEqual(id(self.tstspc.steps[0]), self.stp3_2) self.assertEqual(id(self.tstspc.steps[1]), self.stp1) self.assertEqual(id(self.tstspc.steps[2]), self.stp2) self.assertEqual(id(self.tstspc.steps[3]), self.stp3) self.assertEqual(id(self.tstspc.steps[4]), self.stp3_1) self.assertEqual(id(self.tstspc.steps[5]), self.stp3_3) self.assertEqual(id(self.tstspc.steps[6]), self.stp3_4) self.assertEqual(id(self.tstspc.steps[7]), self.stp3_5) self.assertEqual(id(self.tstspc.steps[8]), self.stp4) def test_move_step_to_top_locked(self): """ It should not matter if the test is unlocked, if moving to the top. Move step 3.2 to the top. """ logger.info('Running {}'.format(self._testMethodName)) self.tstspc.primary_counter_locked = True # verify the numeration of the steps before adding a new step self.assertEqual(self.tstspc.steps[0].step_number, '1.0') self.assertEqual(self.tstspc.steps[1].step_number, '2.0') self.assertEqual(self.tstspc.steps[2].step_number, '3.0') self.assertEqual(self.tstspc.steps[3].step_number, '3.1') self.assertEqual(self.tstspc.steps[4].step_number, '3.2') self.assertEqual(self.tstspc.steps[5].step_number, '3.3') self.assertEqual(self.tstspc.steps[6].step_number, '3.4') self.assertEqual(self.tstspc.steps[7].step_number, '3.5') self.assertEqual(self.tstspc.steps[8].step_number, '4.0') logger.info('moving step 3.2 to the top') step_to_move_index = self.tstspc.get_step_index('3.2') self.tstspc.move_step(step_to_move_index, 0) # verify the numeration of the steps self.assertEqual(self.tstspc.steps[0].step_number, '1.0') self.assertEqual(self.tstspc.steps[1].step_number, '2.0') self.assertEqual(self.tstspc.steps[2].step_number, '3.0') self.assertEqual(self.tstspc.steps[3].step_number, '4.0') self.assertEqual(self.tstspc.steps[4].step_number, '4.1') self.assertEqual(self.tstspc.steps[5].step_number, '4.2') self.assertEqual(self.tstspc.steps[6].step_number, '4.3') self.assertEqual(self.tstspc.steps[7].step_number, '4.4') self.assertEqual(self.tstspc.steps[8].step_number, '5.0') # verify the order of the steps, by comparing the IDs of the Python objects self.assertEqual(id(self.tstspc.steps[0]), self.stp3_2) self.assertEqual(id(self.tstspc.steps[1]), self.stp1) self.assertEqual(id(self.tstspc.steps[2]), self.stp2) self.assertEqual(id(self.tstspc.steps[3]), self.stp3) self.assertEqual(id(self.tstspc.steps[4]), self.stp3_1) self.assertEqual(id(self.tstspc.steps[5]), self.stp3_3) self.assertEqual(id(self.tstspc.steps[6]), self.stp3_4) self.assertEqual(id(self.tstspc.steps[7]), self.stp3_5) self.assertEqual(id(self.tstspc.steps[8]), self.stp4) def test_move_step_upwards_unlocked(self): """ Move step 3.2 to above step 3.0. """ logger.info('Running {}'.format(self._testMethodName)) self.tstspc.primary_counter_locked = False # verify the numeration of the steps before adding a new step self.assertEqual(self.tstspc.steps[0].step_number, '1.0') self.assertEqual(self.tstspc.steps[1].step_number, '2.0') self.assertEqual(self.tstspc.steps[2].step_number, '3.0') self.assertEqual(self.tstspc.steps[3].step_number, '3.1') self.assertEqual(self.tstspc.steps[4].step_number, '3.2') self.assertEqual(self.tstspc.steps[5].step_number, '3.3') self.assertEqual(self.tstspc.steps[6].step_number, '3.4') self.assertEqual(self.tstspc.steps[7].step_number, '3.5') self.assertEqual(self.tstspc.steps[8].step_number, '4.0') logger.info('move step 3.2 to above step 3.0.') step_to_move_index = self.tstspc.get_step_index('3.2') self.tstspc.move_step(step_to_move_index, 2) # verify the numeration of the steps self.assertEqual(self.tstspc.steps[0].step_number, '1.0') self.assertEqual(self.tstspc.steps[1].step_number, '2.0') self.assertEqual(self.tstspc.steps[2].step_number, '3.0') self.assertEqual(self.tstspc.steps[3].step_number, '4.0') self.assertEqual(self.tstspc.steps[4].step_number, '4.1') self.assertEqual(self.tstspc.steps[5].step_number, '4.2') self.assertEqual(self.tstspc.steps[6].step_number, '4.3') self.assertEqual(self.tstspc.steps[7].step_number, '4.4') self.assertEqual(self.tstspc.steps[8].step_number, '5.0') # verify the order of the steps, by comparing the IDs of the Python objects self.assertEqual(id(self.tstspc.steps[0]), self.stp1) self.assertEqual(id(self.tstspc.steps[1]), self.stp2) self.assertEqual(id(self.tstspc.steps[2]), self.stp3_2) self.assertEqual(id(self.tstspc.steps[3]), self.stp3) self.assertEqual(id(self.tstspc.steps[4]), self.stp3_1) self.assertEqual(id(self.tstspc.steps[5]), self.stp3_3) self.assertEqual(id(self.tstspc.steps[6]), self.stp3_4) self.assertEqual(id(self.tstspc.steps[7]), self.stp3_5) self.assertEqual(id(self.tstspc.steps[8]), self.stp4) def test_move_step_upwards_locked(self): """ Move step 3.2 to above step 3.0. """ logger.info('Running {}'.format(self._testMethodName)) self.tstspc.primary_counter_locked = True # verify the numeration of the steps before adding a new step self.assertEqual(self.tstspc.steps[0].step_number, '1.0') self.assertEqual(self.tstspc.steps[1].step_number, '2.0') self.assertEqual(self.tstspc.steps[2].step_number, '3.0') self.assertEqual(self.tstspc.steps[3].step_number, '3.1') self.assertEqual(self.tstspc.steps[4].step_number, '3.2') self.assertEqual(self.tstspc.steps[5].step_number, '3.3') self.assertEqual(self.tstspc.steps[6].step_number, '3.4') self.assertEqual(self.tstspc.steps[7].step_number, '3.5') self.assertEqual(self.tstspc.steps[8].step_number, '4.0') logger.info('move step 3.2 to above step 3.0.') step_to_move_index = self.tstspc.get_step_index('3.2') self.tstspc.move_step(step_to_move_index, 2) # verify the numeration of the steps self.assertEqual(self.tstspc.steps[0].step_number, '1.0') self.assertEqual(self.tstspc.steps[1].step_number, '2.0') self.assertEqual(self.tstspc.steps[2].step_number, '2.1') self.assertEqual(self.tstspc.steps[3].step_number, '3.0') self.assertEqual(self.tstspc.steps[4].step_number, '3.1') self.assertEqual(self.tstspc.steps[5].step_number, '3.2') self.assertEqual(self.tstspc.steps[6].step_number, '3.3') self.assertEqual(self.tstspc.steps[7].step_number, '3.4') self.assertEqual(self.tstspc.steps[8].step_number, '4.0') # verify the order of the steps, by comparing the IDs of the Python objects self.assertEqual(id(self.tstspc.steps[0]), self.stp1) self.assertEqual(id(self.tstspc.steps[1]), self.stp2) self.assertEqual(id(self.tstspc.steps[2]), self.stp3_2) self.assertEqual(id(self.tstspc.steps[3]), self.stp3) self.assertEqual(id(self.tstspc.steps[4]), self.stp3_1) self.assertEqual(id(self.tstspc.steps[5]), self.stp3_3) self.assertEqual(id(self.tstspc.steps[6]), self.stp3_4) self.assertEqual(id(self.tstspc.steps[7]), self.stp3_5) self.assertEqual(id(self.tstspc.steps[8]), self.stp4) def test_move_step_downwards_unlocked(self): """ Move step 3.2 to below step 4.0. """ logger.info('Running {}'.format(self._testMethodName)) self.tstspc.primary_counter_locked = False # verify the numeration of the steps before adding a new step self.assertEqual(self.tstspc.steps[0].step_number, '1.0') self.assertEqual(self.tstspc.steps[1].step_number, '2.0') self.assertEqual(self.tstspc.steps[2].step_number, '3.0') self.assertEqual(self.tstspc.steps[3].step_number, '3.1') self.assertEqual(self.tstspc.steps[4].step_number, '3.2') self.assertEqual(self.tstspc.steps[5].step_number, '3.3') self.assertEqual(self.tstspc.steps[6].step_number, '3.4') self.assertEqual(self.tstspc.steps[7].step_number, '3.5') self.assertEqual(self.tstspc.steps[8].step_number, '4.0') logger.info('Move step 3.2 to below step 4.0.') step_to_move_index = self.tstspc.get_step_index('3.2') self.tstspc.move_step(step_to_move_index, 9) # verify the numeration of the steps self.assertEqual(self.tstspc.steps[0].step_number, '1.0') self.assertEqual(self.tstspc.steps[1].step_number, '2.0') self.assertEqual(self.tstspc.steps[2].step_number, '3.0') self.assertEqual(self.tstspc.steps[3].step_number, '3.1') self.assertEqual(self.tstspc.steps[4].step_number, '3.2') self.assertEqual(self.tstspc.steps[5].step_number, '3.3') self.assertEqual(self.tstspc.steps[6].step_number, '3.4') self.assertEqual(self.tstspc.steps[7].step_number, '4.0') self.assertEqual(self.tstspc.steps[8].step_number, '5.0') # verify the order of the steps, by comparing the IDs of the Python objects self.assertEqual(id(self.tstspc.steps[0]), self.stp1) self.assertEqual(id(self.tstspc.steps[1]), self.stp2) self.assertEqual(id(self.tstspc.steps[2]), self.stp3) self.assertEqual(id(self.tstspc.steps[3]), self.stp3_1) self.assertEqual(id(self.tstspc.steps[4]), self.stp3_3) self.assertEqual(id(self.tstspc.steps[5]), self.stp3_4) self.assertEqual(id(self.tstspc.steps[6]), self.stp3_5) self.assertEqual(id(self.tstspc.steps[7]), self.stp4) self.assertEqual(id(self.tstspc.steps[8]), self.stp3_2) def test_move_step_downwards_locked(self): """ Move step 3.2 to below step 4.0. """ logger.info('Running {}'.format(self._testMethodName)) self.tstspc.primary_counter_locked = True # verify the numeration of the steps before adding a new step self.assertEqual(self.tstspc.steps[0].step_number, '1.0') self.assertEqual(self.tstspc.steps[1].step_number, '2.0') self.assertEqual(self.tstspc.steps[2].step_number, '3.0') self.assertEqual(self.tstspc.steps[3].step_number, '3.1') self.assertEqual(self.tstspc.steps[4].step_number, '3.2') self.assertEqual(self.tstspc.steps[5].step_number, '3.3') self.assertEqual(self.tstspc.steps[6].step_number, '3.4') self.assertEqual(self.tstspc.steps[7].step_number, '3.5') self.assertEqual(self.tstspc.steps[8].step_number, '4.0') logger.info('Move step 3.2 to below step 4.0.') step_to_move_index = self.tstspc.get_step_index('3.2') self.tstspc.move_step(step_to_move_index, 9) # verify the numeration of the steps self.assertEqual(self.tstspc.steps[0].step_number, '1.0') self.assertEqual(self.tstspc.steps[1].step_number, '2.0') self.assertEqual(self.tstspc.steps[2].step_number, '3.0') self.assertEqual(self.tstspc.steps[3].step_number, '3.1') self.assertEqual(self.tstspc.steps[4].step_number, '3.2') self.assertEqual(self.tstspc.steps[5].step_number, '3.3') self.assertEqual(self.tstspc.steps[6].step_number, '3.4') self.assertEqual(self.tstspc.steps[7].step_number, '4.0') self.assertEqual(self.tstspc.steps[8].step_number, '4.1') # verify the order of the steps, by comparing the IDs of the Python objects self.assertEqual(id(self.tstspc.steps[0]), self.stp1) self.assertEqual(id(self.tstspc.steps[1]), self.stp2) self.assertEqual(id(self.tstspc.steps[2]), self.stp3) self.assertEqual(id(self.tstspc.steps[3]), self.stp3_1) self.assertEqual(id(self.tstspc.steps[4]), self.stp3_3) self.assertEqual(id(self.tstspc.steps[5]), self.stp3_4) self.assertEqual(id(self.tstspc.steps[6]), self.stp3_5) self.assertEqual(id(self.tstspc.steps[7]), self.stp4) self.assertEqual(id(self.tstspc.steps[8]), self.stp3_2) # @unittest.skip("demonstrating skipping") class TestRenumberSteps(unittest.TestCase): """ Test if the method to renumber all steps works """ def setUp(self) -> None: logger.info('setUp: {}'.format(self._testMethodName)) logger.debug('create a test specification and add steps') self.tstspc = TestSequence() logger.debug('Python object ID of the test specification: {}'.format(id(self.tstspc))) # create a new step and append it to the steps list number_new_step = create_step_number(1, 0) step_1 = Step(step_num=number_new_step, test_seq=self.tstspc) self.tstspc.steps.append(step_1) self.stp1 = id(step_1) # create a new step and append it to the steps list number_new_step = create_step_number(2, 0) step_2 = Step(step_num=number_new_step, test_seq=self.tstspc) self.tstspc.steps.append(step_2) self.stp2 = id(step_2) # create a new step and append it to the steps list number_new_step = create_step_number(3, 0) step_3 = Step(step_num=number_new_step, test_seq=self.tstspc) self.tstspc.steps.append(step_3) self.stp3 = id(step_3) # create a new step and append it to the steps list number_new_step = create_step_number(3, 1) step_3_1 = Step(step_num=number_new_step, test_seq=self.tstspc) self.tstspc.steps.append(step_3_1) self.stp3_1 = id(step_3_1) # create a new step and append it to the steps list number_new_step = create_step_number(3, 2) step_3_2 = Step(step_num=number_new_step, test_seq=self.tstspc) self.tstspc.steps.append(step_3_2) self.stp3_2 = id(step_3_2) # create a new step and append it to the steps list number_new_step = create_step_number(3, 3) step_3_3 = Step(step_num=number_new_step, test_seq=self.tstspc) self.tstspc.steps.append(step_3_3) self.stp3_3 = id(step_3_3) # create a new step and append it to the steps list number_new_step = create_step_number(3, 4) step_3_4 = Step(step_num=number_new_step, test_seq=self.tstspc) self.tstspc.steps.append(step_3_4) self.stp3_4 = id(step_3_4) # create a new step and append it to the steps list number_new_step = create_step_number(3, 5) step_3_5 = Step(step_num=number_new_step, test_seq=self.tstspc) self.tstspc.steps.append(step_3_5) self.stp3_5 = id(step_3_5) # create a new step and append it to the steps list number_new_step = create_step_number(4, 0) step_4 = Step(step_num=number_new_step, test_seq=self.tstspc) self.tstspc.steps.append(step_4) self.stp4 = id(step_4) def tearDown(self) -> None: logger.info('tearDown: after running test method: {}'.format(self._testMethodName)) # save the test specification as JSON file json_file = os.path.join(confignator.get_option('logging', 'log-dir'), 'tst/data_model/' + self._testMethodName + '.json') with open(json_file, 'w') as fileobject: data = self.tstspc.encode_to_json() fileobject.write(data) logger.debug('Test specification was dumped as JSON: {}'.format(json_file)) def test_move_step_to_top_unlocked(self): """ It should not matter if the test is unlocked, if moving to the top. Move step 3.2 to the top. """ logger.info('Running {}'.format(self._testMethodName)) self.tstspc.primary_counter_locked = False # verify the numeration of the steps before adding a new step self.assertEqual(self.tstspc.steps[0].step_number, '1.0') self.assertEqual(self.tstspc.steps[1].step_number, '2.0') self.assertEqual(self.tstspc.steps[2].step_number, '3.0') self.assertEqual(self.tstspc.steps[3].step_number, '3.1') self.assertEqual(self.tstspc.steps[4].step_number, '3.2') self.assertEqual(self.tstspc.steps[5].step_number, '3.3') self.assertEqual(self.tstspc.steps[6].step_number, '3.4') self.assertEqual(self.tstspc.steps[7].step_number, '3.5') self.assertEqual(self.tstspc.steps[8].step_number, '4.0') # renumber the steps logger.info('renumber the steps') self.tstspc.renumber_steps() # verify the numeration of the steps self.assertEqual(self.tstspc.steps[0].step_number, '1.0') self.assertEqual(self.tstspc.steps[1].step_number, '2.0') self.assertEqual(self.tstspc.steps[2].step_number, '3.0') self.assertEqual(self.tstspc.steps[3].step_number, '4.0') self.assertEqual(self.tstspc.steps[4].step_number, '5.0') self.assertEqual(self.tstspc.steps[5].step_number, '6.0') self.assertEqual(self.tstspc.steps[6].step_number, '7.0') self.assertEqual(self.tstspc.steps[7].step_number, '8.0') self.assertEqual(self.tstspc.steps[8].step_number, '9.0') def create_start_sequence_step(seq_to_start: int) -> Step: """ Creates a Step instance which has the purpose to be the starting point of a sequence. :param: int seq_to_start: the number of the sequence which should be started. """ assert isinstance(seq_to_start, int) s = Step() s.start_sequence = seq_to_start s.description = _('Start sequence {}'.format(seq_to_start)) s.command_code = '#ToDo' return s def create_stop_sequence_step(seq_to_stop: int) -> Step: """ Creates a Step instance which has the purpose to trigger the stop of a sequence. :param: int seq_to_stop: the number of the sequence which should be stopped. """ assert isinstance(seq_to_stop, int) s = Step() s.start_sequence = seq_to_stop s.description = _('Stop sequence {}'.format(seq_to_stop)) s.command_code = '#ToDo' return s if __name__ == '__main__': # create a log file for the self testing log_path = confignator.get_option('logging', 'log-dir') log_file = os.path.join(log_path, 'tst/data_model/data_model.log') logger.addHandler(hdlr=console_hdlr) file_hdlr = toolbox.create_file_handler(file=log_file) logger.addHandler(hdlr=file_hdlr) test_program = unittest.main(verbosity=2, exit=False) logger.info('Ran {} tests'.format(test_program.result.testsRun)) if test_program.result.wasSuccessful(): logger.info('Self-test program was successful.') else: logger.critical('Self-test program failed.') if len(test_program.result.failures) > 0: logger.error('{} tests failed'.format(len(test_program.result.failures))) for fail in test_program.result.failures: logger.error(fail[0]) logger.debug(fail[1])