Skip to content
Snippets Groups Projects
Commit 79519070 authored by brydenf's avatar brydenf
Browse files

Pylint accepts all files....for now.

parent 82bbded9
No related branches found
No related tags found
No related merge requests found
*.xlsx
__pycache__/
.vscode/
\ No newline at end of file
from bs4 import BeautifulSoup
from sys import argv
from testEntry import TestEntry
from writeExcel import ExcelWriter
from sys import exit
import re
usage_str = """
=====================================
Robot Test Reporter written in Python
=====================================
Usage
python parseTestResults.py output.xml
where output.xml is the xml file generated by robot
The command outputs to a new xlslx file if it does not exist, or
appends to an existing one.
"""
class TestOutputParser:
def __init__(self, fname):
self.testEntries = []
self.load_file(fname)
def load_file(self, fname):
self.contents = ""
with open(fname, "r", encoding="utf8") as f:
self.contents = f.read()
if self.contents == "":
print("Empty file {}".format(fname))
exit(-1)
def run_parser(self):
soup = BeautifulSoup(self.contents, "lxml")
# Suite information
suite = soup.find("suite")
path = suite["source"]
# TODO This might be an issue later on. In Unix-style paths the separator is a forward slash
parts = path.split("\\")
# Extract info for test entries
self.api = parts[len(parts) - 2]
self.robotFile = parts[len(parts) - 1]
# Tests
tests = soup.find_all("test")
for test in tests:
self.testEntries.append(self.createTestEntry(test))
# Write tests
ew = ExcelWriter()
for entry in self.testEntries:
ew.writeTestEntry(entry)
ew.save()
def createTestEntry(self, xmlObj):
"""
Takes the xml entry corresponding to the test from the output file,
and returns a TestEntry object with the relevant information extracted.
"""
# retrieve ID and name
idRaw = xmlObj.find("doc", recursive=False).contents
mg = re.search(r"Test ID: ([0-9\.]*)$", idRaw[0].string, re.MULTILINE)
testId = mg.group(1)
name = xmlObj["name"]
#retrieve status and error message (if FAIL)
statusObj = xmlObj.find("status", recursive=False)
cts = statusObj.contents
errorMsg = cts[0] if len(cts) > 0 else ""
result = statusObj["status"]
return TestEntry(testId, name, result, errorMsg, self.api, self.robotFile)
def display_usage():
print(usage_str)
if __name__ == "__main__":
if len(argv) < 2:
display_usage()
exit()
TestOutputParser(argv[1]).run_parser()
\ No newline at end of file
from bs4 import BeautifulSoup
from sys import argv
from testEntry import TestEntry
from writeExcel import ExcelWriter
"""
Tool entry point
"""
import sys
import re
import argparse
usage_str = """
=====================================
Robot Test Reporter written in Python
=====================================
Usage
python parseTestResults.py output.xml
where output.xml is the xml file generated by robot
from bs4 import BeautifulSoup
from test_entry import TestEntry
from write_excel import ExcelWriter
The command outputs to a new xlslx file if it does not exist, or
appends to an existing one.
"""
class TestOutputParser:
def __init__(self, fname):
"""
Parser taking a file name (of the XML output from robot),
and extracts relevant information, eventually creating a list
of TestEntry objects
"""
def __init__(self, input_file, output_file):
self.test_entries = []
self.load_file(fname)
self.load_file(input_file)
self.api = ""
self.output_file = output_file
self.robot_file_contents = ""
def load_file(self, fname):
self.contents = ""
"""
Load xml file
"""
with open(fname, "r", encoding="utf8") as f:
self.contents = f.read()
with open(fname, "r", encoding="utf8") as robot_file:
self.robot_file_contents = robot_file.read()
if self.contents == "":
if self.robot_file_contents == "":
print("Empty file {}".format(fname))
exit(-1)
sys.exit(-1)
def run_parser(self):
soup = BeautifulSoup(self.contents, "lxml")
"""
Run parser, extracting all info to create the test entries
"""
soup = BeautifulSoup(self.robot_file_contents, "lxml")
# Suite information
suite = soup.find("suite")
path = suite["source"]
# TODO This might be an issue later on. In Unix-style paths the separator is a forward slash
parts = path.split("\\")
# Extract info for test entries
self.api = parts[len(parts) - 2]
self.robot_file = parts[len(parts) - 1]
self.robot_file_contents = parts[len(parts) - 1]
# Tests
tests = soup.find_all("test")
......@@ -54,10 +57,10 @@ class TestOutputParser:
self.test_entries.append(self.create_test_entry(test))
# Write tests
ew = ExcelWriter()
excel_writer = ExcelWriter(self.output_file)
for entry in self.test_entries:
ew.write_test_entry(entry)
ew.save()
excel_writer.write_test_entry(entry)
excel_writer.save()
def create_test_entry(self, xml_obj):
"""
......@@ -66,44 +69,28 @@ class TestOutputParser:
"""
# retrieve ID and name
id_raw = xml_obj.find("doc", recursive=False).contents
mg = re.search(r"Test ID: ([0-9\.]*)$", id_raw[0].string, re.MULTILINE)
test_id = mg.group(1)
match_group = re.search(r"Test ID: ([0-9\.]*)$", id_raw[0].string, re.MULTILINE)
test_id = match_group.group(1)
name = xml_obj["name"]
#retrieve status and error message (if FAIL)
statusObj = xml_obj.find("status", recursive=False)
cts = statusObj.contents
status_obj = xml_obj.find("status", recursive=False)
cts = status_obj.contents
error_msg = cts[0] if len(cts) > 0 else ""
result = statusObj["status"]
return TestEntry(test_id, name, result, error_msg, self.api, self.robot_file)
def display_usage():
print(usage_str)
result = status_obj["status"]
return TestEntry(test_id, name, (result, error_msg), (self.api, self.robot_file_contents))
if __name__ == "__main__":
usage_str = """
=====================================
Robot Test Reporter written in Python
=====================================
Usage
python parseTestResults.py output.xml
where output.xml is the xml file generated by robot
The command outputs to a new xlslx file if it does not exist, or
appends to an existing one.
"""
parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('integers', metavar='N', type=int, nargs='+',
help='an integer for the accumulator')
parser.add_argument('-o', dest='fname', action='store_const',
default='testResults.xlsx',
parser = argparse.ArgumentParser(description='Robot Test Reporter written in Python.\n'
'Produces an xlsx file from a robot XML '
'output file.\n'
'The command outputs to a new xlslx file if it '
'does not exist, or appends to an existing one.')
parser.add_argument('input_file', help='XML file generated by Robot')
parser.add_argument('-o', dest='output_file', default='testResults.xlsx',
help='output file name (default: testResults.xlsx)')
args = parser.parse_args()
TestOutputParser(argv[1]).run_parser()
TestOutputParser(args.input_file, args.output_file).run_parser()
class TestEntry:
def __init__(self, id, name, result, errorMsg, api, robotFile):
self.id = id
self.name = name
self.result = result
self.errorMsg = errorMsg
self.api = api
self.robotFile = robotFile
def __str__(self):
baseStr = "[{}] {}:{}".format(self.id, self.name, self.result)
if self.errorMsg != "":
return "{}: {} ({}/{})".format(baseStr, self.errorMsg, self.api, self.robotFile)
else:
return baseStr
def __repr__(self):
return "{}: {}".format(self.name, self.result)
def asList(self):
return [self.id, self.name, self.result, self.errorMsg, self.api, self.robotFile]
\ No newline at end of file
"""
Contains utility class `TestEntry`
"""
class TestEntry:
"""
Models a single entry (row) in the resulting xlsx file.
Contains all the relevant information about a single test:
id: Test ID
name: Test Name
result: PASS/FAIL
error_message: In case of FAIL, contains reason
robot_file: Robot file containing the test
"""
def __init__(self, test_id, name, status, test_info):
self.test_id = test_id
self.name = name
self.result = status[0]
self.error_message = status[1]
self.api = test_info[0]
self.robot_file = test_info[1]
def __str__(self):
base_str = "[{}] {}:{}".format(self.test_id, self.name, self.result)
if self.error_message != "":
return "{}: {} ({}/{})".format(base_str, self.error_message, self.api, self.robot_file)
return base_str
def __repr__(self):
return "{}: {}".format(self.name, self.result)
def as_list(self):
"""
Construct a list-representation of the entry
"""
return [self.test_id, self.name, self.result, self.error_message, self.api, self.robot_file]
"""
Contains utility class for writing to xlsx
"""
from openpyxl import Workbook, load_workbook
from openpyxl.utils import get_column_letter
from openpyxl.styles import Font
from openpyxl.styles.fills import Stop, PatternFill
fname = 'testResults.xlsx'
from openpyxl.styles.fills import PatternFill
class ExcelWriter:
"""
......@@ -12,11 +14,12 @@ class ExcelWriter:
"""
PASS_COL = "00FF00"
FAIL_COL = "FF0000"
def __init__(self):
self.wb = getWorkbook()
self.ws = self.wb.active
def get_entry_with_id(self, id):
def __init__(self, output_file):
self.work_book = self.get_workbook()
self.work_sheet = self.work_book.active
self.output_file = output_file
def get_entry_with_id(self, test_id):
"""
When inserting an entry, it might be a test being re-run. In that case,
the row containing that test needs to be updated, as opposed to appending
......@@ -24,21 +27,27 @@ class ExcelWriter:
This method finds that row, returning the row number,
or -1 in the case where that test is not in the report yet.
"""
for cell in self.ws["A"]:
for cell in self.work_sheet["A"]:
if cell.value is None:
return -1
elif cell.value == id:
print("MATCH with id {} at row {}".format(id, cell.row))
if cell.value == test_id:
print("MATCH with id {} at row {}".format(test_id, cell.row))
return cell.row
return -1
def get_last_row(self):
for cell in self.ws["A"]:
"""
return index of first empty row
"""
for cell in self.work_sheet["A"]:
if cell.value is None:
return cell.row
return cell.row + 1
def writeTestEntry(self, test_entry):
return 1
def write_test_entry(self, test_entry):
"""
Write a test entry to the work_sheet
"""
existing_entry_row = self.get_entry_with_id(test_entry.id)
last_row = self.get_last_row()
# Use the above two values to pick a row
......@@ -48,38 +57,47 @@ class ExcelWriter:
cell_col = ExcelWriter.PASS_COL if test_entry.result == "PASS" else ExcelWriter.FAIL_COL
# Test entry as a list
entry_vals = test_entry.asList()
entry_vals = test_entry.as_list()
for col, cell_vale in zip(self.ws.iter_cols(min_row=entry_row, max_col=len(entry_vals), max_row=entry_row), entry_vals):
for col, cell_vale in zip(self.work_sheet.iter_cols(min_row=entry_row,
max_col=len(entry_vals),
max_row=entry_row), entry_vals):
for cell in col:
cell.value = cell_vale
cell.fill = PatternFill("solid", fgColor=cell_col)# cellCol
def get_workbook(self):
"""
Returns workbook at output_file/.
Creates and initialises it (adds headers) if it doesn't exist yet.
"""
try:
work_book = load_workbook(filename=self.output_file)
return work_book
except FileNotFoundError:
work_book = Workbook()
init_workbook(work_book)
return work_book
def save(self):
self.wb.save(filename = fname)
"""
Save workbook to disk.
"""
self.work_book.save(filename=self.output_file)
def initWorkbook(wb):
def init_workbook(work_book):
"""
Writes column headers to ws
Writes column headers to ws.
"""
headers = [("Test ID", 10), ("Test name", 80), ("Result", 6), ("Error Message", 100), ("NFV API", 25), ("Robot Test File", 25)]
headerFont = Font(bold=True)
ws = wb.active
for col, header in zip(ws.iter_cols(min_row=1, max_col=len(headers), max_row=1), headers):
headers = [("Test ID", 10), ("Test name", 80), ("Result", 6), ("Error Message", 100),
("NFV API", 25), ("Robot Test File", 25)]
header_font = Font(bold=True)
work_sheet = work_book.active
for col, header in zip(work_sheet.iter_cols(min_row=1, max_col=len(headers),
max_row=1), headers):
for cell in col:
headerName = header[0]
header_name = header[0]
colsize = header[1]
ws.column_dimensions[get_column_letter(cell.column)].width = colsize
cell.value = headerName
cell.font = headerFont
def getWorkbook():
try:
wb = load_workbook(filename = fname)
return wb
except FileNotFoundError:
wb = Workbook()
initWorkbook(wb)
return wb
\ No newline at end of file
work_sheet.column_dimensions[get_column_letter(cell.column)].width = colsize
cell.value = header_name
cell.font = header_font
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment