| #!/usr/bin/env python |
| |
| r""" |
| This module contains keyword functions to supplement robot's built in |
| functions and use in test where generic robot keywords don't support. |
| """ |
| |
| try: |
| from robot.libraries.BuiltIn import BuiltIn |
| from robot.libraries import DateTime |
| except ImportError: |
| pass |
| import time |
| import os |
| import difflib |
| |
| |
| ########################################################################## |
| def json_inv_file_diff_check(file1_path, |
| file2_path, |
| diff_file_path, |
| skip_string): |
| r""" |
| Compare the contents of two files which contain inventory data in |
| JSON format. The comparison is similar to the unix 'diff' command but |
| the output lists the hardware subsystem (category) where differences |
| are found, and some differences are selectively ignored. The items |
| ignored are defined by the skip_string. |
| |
| Description of arguments: |
| file1_path File containing JSON formatted data. |
| file2_path File to compare to file1 to. |
| diff_file_path File which will contain the resulting difference report. |
| skip_string String which defines what inventory items |
| to ignore if there are differences in inventory |
| files -- some differences are expected or |
| immaterial. For example, assigned processor |
| speed routinely varies depending upon the |
| needs of OCC/tmgt/ondemand governor. |
| Back-to-back inventory runs may show |
| processor speed differences even if nothing |
| else was run between them. |
| Each item in this string is of the form |
| category:leafname where category is a JSON |
| hardware category such as "processor", "memory", |
| "disk", "display", "network", etc., |
| and leafname is a leaf node (atribute name) |
| within that category. |
| For example: "processor:size", or |
| "processor:size,network:speed,display:id". |
| |
| Sample difference report: |
| Difference at line 102 (in section "memory":) |
| 102 - "slot": "UOPWR.BAR.1315ACA-DIMM0", |
| 102 + "slot": "0" |
| Difference at line 126 (in section "processor":) |
| 126 - "size": 2151000000, +++ NOTE! This is an ignore item |
| 126 + "size": 2201000000, +++ NOTE! This is an ignore item |
| |
| Returns: |
| 0 if both files contain the same information or they differ only in |
| items specified as those to ignore. |
| 2 if FILES_DO_NOT_MATCH. |
| 3 if INPUT_FILE_DOES_NOT_EXIST. |
| 4 if IO_EXCEPTION_READING_FILE. |
| 5 if IO_EXCEPTION_WRITING_FILE. |
| """ |
| |
| FILES_MATCH = 0 |
| FILES_DO_NOT_MATCH = 2 |
| INPUT_FILE_DOES_NOT_EXIST = 3 |
| IO_EXCEPTION_READING_FILE = 4 |
| IO_EXCEPTION_WRITING_FILE = 5 |
| |
| # Hardware categories which are reported in the JSON inventory files. |
| hardware_categories = ['\"processor\":', '\"memory\":', '\"disk\":', |
| '\"I/O\":', '\"display\":', '\"generic\":', |
| '\"network\":', '\"communication\":', |
| '\"printer\":', '\"input\":', '\"multimedia\":', |
| '\"tape\":'] |
| |
| # The minimum size in bytes a JSON file must be. |
| min_json_byte_size = 16 |
| |
| now = time.strftime("At %Y-%m-%d %H:%M:%S") |
| |
| if (not os.path.exists(file1_path) or (not os.path.exists(file2_path))): |
| return INPUT_FILE_DOES_NOT_EXIST |
| try: |
| with open(file1_path, 'r') as file: |
| initial = file.readlines() |
| with open(file2_path, 'r') as file: |
| final = file.readlines() |
| except IOError: |
| file.close() |
| return IO_EXCEPTION_READING_FILE |
| except ValueError: |
| file.close() |
| return INPUT_FILE_MALFORMED |
| else: |
| file.close() |
| |
| # Must have more than a trivial number of bytes. |
| if len(initial) <= min_json_byte_size: |
| return INPUT_FILE_MALFORMED |
| |
| if (initial == final): |
| try: |
| file = open(diff_file_path, 'w') |
| except IOError: |
| file.close() |
| line_to_print = "Specified skip (ignore) string = " + \ |
| skip_string + "\n\n" |
| file.write(line_to_print) |
| line_to_print = now + " found no difference between file " + \ |
| file1_path + " and " + \ |
| file2_path + "\n" |
| file.write(line_to_print) |
| file.close() |
| return FILES_MATCH |
| |
| # Find the differences and write difference report to diff_file_path file. |
| try: |
| file = open(diff_file_path, 'w') |
| except IOError: |
| file.close() |
| return IO_EXCEPTION_WRITING_FILE |
| |
| line_to_print = "Specified skip (ignore) string = " + skip_string + "\n\n" |
| file.write(line_to_print) |
| line_to_print = now + " compared files " + \ |
| file1_path + " and " + \ |
| file2_path + "\n" |
| file.write(line_to_print) |
| |
| diff = difflib.ndiff(initial, final) |
| # The diff array contains all lines that match in the |
| # initial and final arrays, and also all lines that differ. |
| # The first two characters of each line |
| # are prefixed with two letters, defined as: |
| # '- ' This line is unique to initial |
| # '+ ' This line is line unique to final |
| # ' ' This line is common to both, and |
| # '? ' This line indicates approximate differences. |
| # For example, comparing two three-line files: |
| # This line is in both initial and final. |
| # This line is too but the next line is different in each file. |
| # - "size": 2101000000, |
| # ? - ^ |
| # + "size": 2002000000, |
| # ? ^^ |
| |
| print_header_flag = False |
| category = "" |
| row_num = 1 |
| item_we_cannot_ignore = False |
| |
| for my_line in diff: |
| diff_item = my_line.strip('\n') |
| # If it's a Category, such as processor or memory, |
| # save it. We will print it out later. |
| # Most lines do not explicitly contain a category. As such the |
| # category for that line is found in a previous line, which |
| # will be the category we last found. |
| for hdw_cat in hardware_categories: |
| if (hdw_cat in diff_item): |
| # If we don't have a match we will reuse |
| # the prvious category. |
| category = hdw_cat |
| # Lines beginning with minus or plus or q-mark are |
| # true difference items. |
| # We want to look at those in more detail. |
| if diff_item.startswith('? '): |
| # we can ignore these |
| continue |
| if (diff_item.startswith('- ') or diff_item.startswith('+ ')): |
| # If we have not printed the header line for this |
| # difference, print it now. |
| if print_header_flag is False: |
| line_to_print = "Difference at line " + \ |
| str(row_num) + " (in section " + \ |
| category + ")\n" |
| file.write(line_to_print) |
| # If this is in the ignore string, we'll print |
| # it but also add text that it is an ignore item. |
| skipitem = False |
| # If a skip_string is specified, check if category and item are |
| # in the skip_string. |
| if skip_string: |
| skip_list = skip_string.split(",") |
| for item in skip_list: |
| cat_and_value = item.split(":") |
| ignore_category = cat_and_value[0].lower().strip() |
| ignore_value = cat_and_value[1].lower().strip() |
| if ((ignore_category in category.lower().strip()) and |
| (ignore_value in diff_item.lower().strip())): |
| line_to_print = " " + \ |
| str(row_num) + " " + diff_item + \ |
| " +++ NOTE! This line matches" + \ |
| " the inventory ignore list and" + \ |
| " can be ignored. +++\n" |
| # Set flag indicating this item is a skip item. |
| skipitem = True |
| break |
| if skipitem is False: |
| # Its not a skip item, that is, |
| # this is not on the ignore list. |
| # Print the item and set the item_we_canot_ignore flag |
| # indicating we have an item not on the ignore list. The |
| # flag will determine the return code we |
| # pass back to the user at the end. |
| item_we_cannot_ignore = True |
| line_to_print = " " + \ |
| str(row_num) + " " + diff_item + "\n" |
| file.write(line_to_print) |
| print_header_flag = True |
| |
| else: |
| # Adjust row numbering as a difference is only one line |
| # but it takes several lines in the diff file. |
| if print_header_flag is True: |
| row_num = row_num + 1 |
| print_header_flag = False |
| row_num = row_num + 1 |
| |
| # Make sure we end the file. |
| file.write("\n") |
| file.close() |
| |
| if item_we_cannot_ignore: |
| # We have at least one diff_item not on the ignore list. |
| return FILES_DO_NOT_MATCH |
| else: |
| # Any differences were on the ignore list. |
| return FILES_MATCH |
| ############################################################################### |
| |
| |
| ############################################################################### |
| |
| |
| def run_until_keyword_fails(retry, |
| retry_interval, |
| name, |
| *args): |
| r""" |
| Execute a robot keyword repeatedly until it either fails or the timeout |
| value is exceeded. |
| Note: Opposite of robot keyword "Wait Until Keyword Succeeds". |
| |
| Description of argument(s): |
| retry Max timeout time in hour(s). |
| retry_interval Time interval in minute(s) for looping. |
| name Robot keyword to execute. |
| args Robot keyword arguments. |
| """ |
| |
| # Convert the retry time in seconds |
| retry_seconds = DateTime.convert_time(retry) |
| timeout = time.time() + int(retry_seconds) |
| |
| # Convert the interval time in seconds |
| interval_seconds = DateTime.convert_time(retry_interval) |
| interval = int(interval_seconds) |
| |
| BuiltIn().log(timeout) |
| BuiltIn().log(interval) |
| |
| while True: |
| status = BuiltIn().run_keyword_and_return_status(name, *args) |
| |
| # Return if keywords returns as failure. |
| if status is False: |
| BuiltIn().log("Failed as expected") |
| return False |
| # Return if retry timeout as success. |
| elif time.time() > timeout > 0: |
| BuiltIn().log("Max retry timeout") |
| return True |
| time.sleep(interval) |
| BuiltIn().log(time.time()) |
| |
| return True |
| ############################################################################### |
| |
| |
| ############################################################################### |
| def htx_error_log_to_list(htx_error_log_output): |
| |
| r""" |
| Parse htx error log output string and return list of strings in the form |
| "<field name>:<field value>". |
| The output of this function may be passed to the build_error_dict function. |
| |
| Description of argument(s): |
| htx_error_log_output Error entry string containing the stdout |
| generated by "htxcmdline -geterrlog". |
| |
| Example of htx_error_log_output contents: |
| |
| ######################## Result Starts Here ############################### |
| Currently running ECG/MDT : /usr/lpp/htx/mdt/mdt.whit |
| =========================== |
| --------------------------------------------------------------------- |
| Device id:/dev/nvidia0 |
| Timestamp:Mar 29 19:41:54 2017 |
| err=00000027 |
| sev=1 |
| Exerciser Name:hxenvidia |
| Serial No:Not Available |
| Part No:Not Available |
| Location:Not Available |
| FRU Number:Not Available |
| Device:Not Available |
| Error Text:cudaEventSynchronize for stopEvent returned err = 0039 from file |
| , line 430. |
| --------------------------------------------------------------------- |
| --------------------------------------------------------------------- |
| Device id:/dev/nvidia0 |
| Timestamp:Mar 29 19:41:54 2017 |
| err=00000027 |
| sev=1 |
| Exerciser Name:hxenvidia |
| Serial No:Not Available |
| Part No:Not Available |
| Location:Not Available |
| FRU Number:Not Available |
| Device:Not Available |
| Error Text:Hardware Exerciser stopped on error |
| --------------------------------------------------------------------- |
| ######################### Result Ends Here ################################ |
| |
| Example output: |
| Returns the lists of error string per entry |
| ['Device id:/dev/nvidia0', |
| 'Timestamp:Mar 29 19:41:54 2017', |
| 'err=00000027', |
| 'sev=1', |
| 'Exerciser Name:hxenvidia', |
| 'Serial No:Not Available', |
| 'Part No:Not Available', |
| 'Location:Not Available', |
| 'FRU Number:Not Available', |
| 'Device:Not Available', |
| 'Error Text:cudaEventSynchronize for stopEvent returned err = 0039 |
| from file , line 430.'] |
| """ |
| |
| # List which will hold all the list of entries. |
| error_list = [] |
| |
| temp_error_list = [] |
| parse_walk = False |
| |
| for line in htx_error_log_output.splitlines(): |
| # Skip lines starting with "#" |
| if line.startswith("#"): |
| continue |
| |
| # Mark line starting with "-" and set parse flag. |
| if line.startswith("-") and parse_walk is False: |
| parse_walk = True |
| continue |
| # Mark line starting with "-" and reset parse flag. |
| # Set temp error list to EMPTY. |
| elif line.startswith("-"): |
| error_list.append(temp_error_list) |
| parse_walk = False |
| temp_error_list = [] |
| # Add entry to list if line is not emtpy |
| elif parse_walk: |
| temp_error_list.append(str(line)) |
| |
| return error_list |
| ############################################################################### |
| |
| |
| ############################################################################### |
| def build_error_dict(htx_error_log_output): |
| |
| r""" |
| Builds error list into a list of dictionary entries. |
| |
| Description of argument(s): |
| error_list Error list entries. |
| |
| Example output dictionary: |
| { |
| 0: |
| { |
| 'sev': '1', |
| 'err': '00000027', |
| 'Timestamp': 'Mar 29 19:41:54 2017', |
| 'Part No': 'Not Available', |
| 'Serial No': 'Not Available', |
| 'Device': 'Not Available', |
| 'FRU Number': 'Not Available', |
| 'Location': 'Not Available', |
| 'Device id': '/dev/nvidia0', |
| 'Error Text': 'cudaEventSynchronize for stopEvent returned err = 0039 |
| from file , line 430.', |
| 'Exerciser Name': 'hxenvidia' |
| }, |
| 1: |
| { |
| 'sev': '1', |
| 'err': '00000027', |
| 'Timestamp': 'Mar 29 19:41:54 2017', |
| 'Part No': 'Not Available', |
| 'Serial No': 'Not Available', |
| 'Device': 'Not Available', |
| 'FRU Number': 'Not Available', |
| 'Location': 'Not Available', |
| 'Device id': '/dev/nvidia0', |
| 'Error Text': 'Hardware Exerciser stopped on error', |
| 'Exerciser Name': 'hxenvidia' |
| } |
| }, |
| |
| """ |
| |
| # List which will hold all the list of entries. |
| error_list = [] |
| error_list = htx_error_log_to_list(htx_error_log_output) |
| |
| # dictionary which holds the error dictionry entry. |
| error_dict = {} |
| |
| temp_error_dict = {} |
| error_index = 0 |
| |
| # Loop through the error list. |
| for entry_list in error_list: |
| # Loop through the first error list entry. |
| for entry in entry_list: |
| # Split string into list for key value update. |
| # Example: 'Device id:/dev/nvidia0' |
| # Example: 'err=00000027' |
| parm_split = re.split("[:=]", entry) |
| # Populate temp dictionary with key value pair data. |
| temp_error_dict[str(parm_split[0])] = parm_split[1] |
| |
| # Update the master dictionary per entry index. |
| error_dict[error_index] = temp_error_dict |
| # Reset temp dict to EMPTY and increment index count. |
| temp_error_dict = {} |
| error_index += 1 |
| |
| return error_dict |
| |
| ############################################################################### |