def _discover(self): # filter decorated aetest methods from helper methods aetest_methods = {} for item in dir(self): if hasattr(getattr(self, item), '__testcls__'): aetest_methods.update({item: getattr(self, item)}) used_uids = [] sections = [] for component in self.parameters.get('test_sections', {}): for action, data in component.items(): # Attempt to find existing method with same name as action method = aetest_methods.get(action) if not method: # The function doesn't exist # Generate the test automatically if 'setup' in data: # Load the setup one method = setup_section elif 'cleanup' in data: # Load the cleanup one method = cleanup_section else: # Default is test method = test_section func = copy_func(method) func.uid = action iteration = 1 while func.uid in used_uids: func.uid = '{}.{}'.format(func.uid, iteration) iteration += 1 func.parameters = ParameterDict() func.parameters['data'] = data func.source = Source(self, method.__class__) new_method = func.__get__(self, func.__testcls__) sections.append(new_method) used_uids.append(new_method.uid) return sections
def discover(self): self.history = OrderedDict() try: order = self.device.clean['order'] except KeyError: raise Exception("Key 'order' is missing for device " "'{d}'".format(d=self.device.name)) # Insert the 'images' value into necessary clean sections if self.device.clean['images']: initialize_clean_sections(self.image_handler, order) # self.device.clean['change_boot_variable'] = {'images': []} all_data = {} all_schema = {} sections = [] common_data = {} self.parameters['common_data'] = common_data for section in order: try: data = self.device.clean[section] or {} except KeyError: # Cannot find section - raise exception raise Exception("Cannot find '{section}' in the provided " "sections even though it was provided in " "the order list '{order}'".\ format(section=section, order=order)) # Load it up # If source isnt provided then check if it is inside the clean json if 'source' not in data: # Check if that one exists in the json task = _get_clean(section, clean_data, self.device) else: task = load_class(data, self.device) # Verify if schema exists for this section if hasattr(task, 'schema'): # if the stage has schema defined then build the bigger schema all_schema[task.__name__] = task.schema all_data[task.__name__] = data # unwrap to get original method, tmp fix need to handle in genie core infra task = unwrap(task) func = copy_func(task) func.uid = task.__name__ func.parameters = ParameterDict() func.parameters['device'] = self.device func.parameters['common_data'] = common_data func.source = Source(self, objcls=func.__class__) for parameter, value in data.items(): func.parameters[parameter] = value # Bind it and append to the section list new_section = func.__get__(self, func.__testcls__) self.history[new_section.uid] = new_section # Add processor, add parameters to it if any if self.device.clean.get('device_recovery'): processor = partial(recovery_processor, **self.device.clean.get('device_recovery')) processors.add(new_section, pre=[block_section], post=[processor], exception=[]) sections.append(new_section) recovery_data = self.device.clean.get('device_recovery') # if recovery info not provided, don't need to check schema if recovery_data: recovery_schema = recovery_processor.schema all_schema['device_recovery'] = recovery_schema all_data['device_recovery'] = recovery_data try: Schema(all_schema).validate(all_data) except SchemaMissingKeyError as e: # proto type raise ValueError( "Clean schema check failed. The following keys are missing from clean yaml file:\n\n{}" .format(self._format_missing_key_msg( e.missing_list))) from None except SchemaTypeError as e: raise TypeError( "Clean schema check failed. Incorrect value type was provided for the " "following key:\n\n{}\n\nExpected type {} but got type {}". format(self._format_missing_key_msg([e.path]), str(e.type), type(e.data))) from None except SchemaUnsupportedKeyError as e: raise ValueError( "Clean schema check failed. The following keys are not supported:\n\n{}" .format(self._format_missing_key_msg( e.unsupported_keys))) from None return sections
def __iter__(self): '''Built-in function __iter__ Generator function, yielding each testable item within this container in the order of appearance inside the test cases. This is the main mechanism that allows looping through CleanTestcase Section's child items. ''' self.discover() used_uids = {} order = self.device.clean['order'] while True: for stage in order: if stage not in self.stages: raise Exception("Stage '{}' has no configuration" " in clean.yaml for device {}".format( stage, self.device.name)) # Check if stage has hit execution limits to protect # against an infinite loop scenario count = len(used_uids.get(stage, [])) limit = self.stages[stage]['stage_reuse_limit'] or \ self.global_stage_reuse_limit if count >= limit: # Dont log this for every remaining stage if not aetest.executer.goto: log.error( banner( REUSE_LIMIT_MSG.format(stage=stage, limit=limit))) aetest.executer.goto_result = results.Blocked aetest.executer.goto = [['Infinite loop scenario', str]] # If image handler has a method to update this section - execute it if self.image_handler: self.image_handler.update_section(stage) cls = self.stages[stage]['func'] # Get a unique ID for the section cls_name = stage.split('__')[0] if cls_name not in used_uids: used_uids[cls_name] = [] cls.uid = cls.__name__ else: cls.uid = f"{cls.__name__}({len(used_uids[cls_name])+1})" used_uids[cls_name].append(cls.uid) cls.parameters = ParameterDict() cls.parameters['device'] = self.device args = self.stages[stage]['args'] for parameter, value in args.items(): cls.parameters[parameter] = value if self.device_recovery_processor: processors.affix(cls, pre=[block_section], post=[self.device_recovery_processor]) cls = cls() cls.__name__ = cls.__uid__ cls.history = self.history cls.history[cls.uid] = cls # Create a stage section new_section = StageSection(cls, parent=self) # For some unknown reason, this is required for internal arguments # like 'steps' and 'section' to be propagated. Do not remove. cls.parameters.internal = new_section.parameters.internal yield new_section pass_order = self.stages[stage]['change_order_if_pass'] if pass_order and new_section.result in [Passed, Passx]: msg = "Due to 'change_order_if_pass' the order of clean " \ "is changed to:\n- " + "\n- ".join(pass_order) log.warning(msg) order = pass_order if self.image_handler: self.image_handler.update_image_references(cls) break fail_order = self.stages[stage]['change_order_if_fail'] if fail_order and new_section.result in [Failed, Errored]: msg = "Due to 'change_order_if_fail' the order of clean " \ "is changed to:\n- " + "\n- ".join(fail_order) log.warning(msg) order = fail_order # In this case we do not want the overall clean result to # be failed. Leave the section result alone but change the # parents to Passed. new_section.parent.parent.result = Passed new_section.parent.result = Passed new_section.result = Passed break if not new_section.result: # Dont log this for every remaining stage if not aetest.executer.goto: log.error(banner("*** Terminating Genie Clean ***")) aetest.executer.goto_result = results.Blocked msg = '{} has {}'.format(new_section.uid, new_section.result) aetest.executer.goto = [[msg, str]] # image handler updates latest image if self.image_handler: self.image_handler.update_image_references(cls) else: # Every stage in 'order' successfully ran # Break from while loop to finish clean break
def __iter__(self): '''Built-in function __iter__ Generator function, yielding each testable item within this container in the order of appearance inside the test cases. This is the main mechanism that allows looping through CleanTestcase Section's child items. ''' self.discover() used_uids = {} order = self.device.clean['order'] while True: for stage in order: if stage not in self.stages: self.failed("Stage '{}' has no configuration" " in clean.yaml for device {}".format( stage, self.device.name)) # Check if stage has hit execution limits to protect # against an infinite loop scenario count = len(used_uids.get(stage, [])) limit = self.stages[stage]['stage_reuse_limit'] or \ self.global_stage_reuse_limit if count >= limit: # Dont log this for every remaining stage if not aetest.executer.goto: log.error( banner( REUSE_LIMIT_MSG.format(stage=stage, limit=limit))) aetest.executer.goto_result = results.Blocked aetest.executer.goto = [['Infinite loop scenario', str]] # If image handler has a method to update this section - execute it if self.image_handler: self.image_handler.update_section(stage) func = self.stages[stage]['func'] # Get a unique ID for the section if stage not in used_uids: used_uids[stage] = [] func.uid = stage else: func.uid = "{}({})".format(stage, len(used_uids[stage]) + 1) used_uids[stage].append(func.uid) # Setup stage function func.source = Source(self, objcls=func.__class__) func.parameters = ParameterDict() func.parameters['device'] = self.device args = self.stages[stage]['args'] for parameter, value in args.items(): func.parameters[parameter] = value # Bind function section = func.__get__(self, func.__testcls__) self.history[section.uid] = section if self.device_recovery_processor: processors.affix(section, pre=[block_section], post=[self.device_recovery_processor], exception=[]) new_section = section.__testcls__(section, parent=self) yield new_section pass_order = self.stages[stage]['change_order_if_pass'] if pass_order and new_section.result in [Passed, Passx]: msg = "Due to 'change_order_if_pass' the order of clean " \ "is changed to:\n- " + "\n- ".join(pass_order) log.warning(msg) order = pass_order break fail_order = self.stages[stage]['change_order_if_fail'] if fail_order and new_section.result in [Failed, Errored]: msg = "Due to 'change_order_if_fail' the order of clean " \ "is changed to:\n- " + "\n- ".join(fail_order) log.warning(msg) order = fail_order # In this case we do not want the overall clean result to # be failed. Leave the section result alone but change the # parents to Passed. new_section.parent.parent.result = Passed new_section.parent.result = Passed new_section.result = Passed break if not new_section.result: # Dont log this for every remaining stage if not aetest.executer.goto: log.error(banner("*** Terminating Genie Clean ***")) aetest.executer.goto_result = results.Blocked msg = '{} has {}'.format(new_section.uid, new_section.result) aetest.executer.goto = [[msg, str]] # image handler updates latest image if self.image_handler: self.image_handler.update_image_references(section) else: # Every stage in 'order' successfully ran # Break from while loop to finish clean break