Skip to content

Commit 245011f

Browse files
authored
Refactor xml writer (#6)
Merge text and file annotations into single class. Remove cleanup of file annotation files as this should be done calling script. * remove cleanup and update write_xml * refactor annotations * fix file annotation cdata * update write xml signature * additional syntax edits * fix annotation attributes * update on reviewers comments
1 parent 3128c53 commit 245011f

File tree

2 files changed

+92
-163
lines changed

2 files changed

+92
-163
lines changed
Lines changed: 78 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from __future__ import print_function
22
import base64
33
import gzip
4-
import ntpath
54
import os
5+
import io
6+
from io import BytesIO
67
import os.path
78
import sys
8-
import tempfile
99
from xml.dom.minidom import parseString
1010

1111

@@ -18,7 +18,7 @@ def __init__(self, name, value):
1818
self.name = name
1919
self.value = value
2020

21-
def _write_xml(self, parent_element, dom):
21+
def write_xml(self, parent_element, dom):
2222
d_elem = dom.createElement('custom_data')
2323
d_elem.setAttribute('name', self.name)
2424
cdata = dom.createCDATASection(self.value)
@@ -33,107 +33,57 @@ def __init__(self, name, comment):
3333

3434

3535
class Annotation:
36-
def __init__(self, level, description):
36+
def __init__(self, name='unknown', level='info', description='',
37+
file_path=None, mime_type='text/plain'):
38+
self.name = name
3739
self.level = level
3840
self.description = description
39-
self.name = ''
41+
self.mimeType = mime_type
42+
self.file_path = file_path
4043
self.comments = []
4144

4245
def add_comment(self, name, comment):
4346
comment = AnnotationComment(name, comment)
4447
self.comments.append(comment)
4548

46-
def _clean_up(self):
47-
pass
48-
49-
50-
class FileAnnotation(Annotation):
51-
def __init__(self, file_path, level='info', description='',
52-
mime_type='text/plain', delete_file=True):
53-
Annotation.__init__(self, level, description)
54-
self.mimeType = mime_type
55-
self.filePath = file_path
56-
# self.filePath = os.path.abspath(FilePath)
57-
# this covers the case where the path ends with a backslash
58-
self.fileName = ntpath.basename(file_path)
59-
self.deleteFile = delete_file
60-
61-
def _clean_up(self):
62-
if self.deleteFile and os.path.isfile(self.filePath):
63-
os.remove(self.filePath)
64-
65-
def _write_xml(self, parent_element, dom):
66-
if not os.path.isfile(self.filePath):
67-
# write as a warning text annotation
68-
ta = TextAnnotation(self.name or self.fileName, self.level)
69-
ta.description = 'File: ' + self.filePath + ' not found.'
70-
for com in self.comments:
71-
ta.comments.append(com)
72-
ta._write_xml(parent_element, dom)
73-
return
74-
75-
# write as a file annotation
76-
anno = dom.createElement("annotation")
77-
anno.setAttribute("default_file_name", "true")
78-
anno.setAttribute("link_file", "false")
79-
anno.setAttribute("description", XmlWriter.fix_line_ends(self.description))
80-
anno.setAttribute("level", self.level)
81-
anno.setAttribute("file", "file://" + self.filePath)
82-
anno.setAttribute("mime_type", self.mimeType)
83-
anno.setAttribute("name", self.name or self.fileName)
84-
85-
if os.path.isfile(self.filePath):
86-
# gzip the file
87-
with open(self.filePath, 'rb') as inFile:
88-
out_file = tempfile.TemporaryFile()
89-
gzip_file_path = out_file.name
90-
out_file.close()
91-
out_file = gzip.open(gzip_file_path, 'wb')
92-
out_file.writelines(inFile)
93-
out_file.close()
94-
inFile.close()
95-
96-
# base64 the file
97-
out_file = open(gzip_file_path, 'rb')
98-
gzip_data = out_file.read()
99-
b64_data = base64.standard_b64encode(gzip_data)
100-
out_file.close()
101-
os.remove(gzip_file_path)
102-
cdata = dom.createCDATASection(b64_data)
103-
anno.appendChild(cdata)
104-
105-
# add comments
106-
for c in self.comments:
107-
c_elem = dom.createElement('comment')
108-
c_elem.setAttribute("label", c.name)
109-
cdata = dom.createCDATASection(c.comment)
110-
c_elem.appendChild(cdata)
111-
anno.appendChild(c_elem)
112-
113-
parent_element.appendChild(anno)
114-
115-
116-
class TextAnnotation(Annotation):
117-
def __init__(self, name, level='info', description=''):
118-
Annotation.__init__(self, level, description)
119-
self.name = name
120-
121-
def _write_xml(self, parent_element, dom):
122-
# write as a file annotation to the root suite
123-
anno = dom.createElement("annotation")
124-
anno.setAttribute("description", XmlWriter.fix_line_ends(self.description))
125-
anno.setAttribute("level", self.level)
126-
anno.setAttribute("name", self.name)
49+
def write_xml(self, parent_element, dom):
50+
annotation = dom.createElement("annotation")
51+
annotation.setAttribute("description", self.description)
52+
annotation.setAttribute("level", self.level)
53+
annotation.setAttribute("name", self.name)
54+
55+
if self.file_path is not None:
56+
if not os.path.isfile(self.file_path):
57+
ta = Annotation(self.file_path, level='error')
58+
ta.description = 'File: ' + self.file_path + ' not found.'
59+
for com in self.comments:
60+
ta.comments.append(com)
61+
ta.write_xml(parent_element, dom)
62+
return
63+
else:
64+
annotation.setAttribute("link_file", "false")
65+
annotation.setAttribute("file", "file://" + self.file_path)
66+
annotation.setAttribute("mime_type", self.mimeType)
67+
with io.open(self.file_path, 'rb') as inFile:
68+
out = BytesIO()
69+
with gzip.GzipFile(fileobj=out, mode="wb") as f:
70+
f.writelines(inFile)
71+
f.close()
72+
gzip_data = out.getvalue()
73+
b64_data = base64.b64encode(gzip_data)
74+
b64_data_string = b64_data.decode()
75+
cdata = dom.createCDATASection(b64_data_string)
76+
annotation.appendChild(cdata)
12777

12878
# add comments
12979
for c in self.comments:
13080
c_elem = dom.createElement('comment')
13181
c_elem.setAttribute("label", c.name)
13282
cdata = dom.createCDATASection(c.comment)
13383
c_elem.appendChild(cdata)
134-
anno.appendChild(c_elem)
84+
annotation.appendChild(c_elem)
13585

136-
parent_element.appendChild(anno)
86+
parent_element.appendChild(annotation)
13787

13888

13989
class TestCase:
@@ -145,34 +95,33 @@ def __init__(self, name, status='passed'):
14595
self.annotations = []
14696
self.start_time = ""
14797
self.duration = 0
148-
# path to file with additional diagnostics
149-
self.diagnostic_file = None
150-
self.meta_info = ""
151-
152-
def _clean_up(self):
153-
for a in self.annotations:
154-
a._clean_up()
15598

15699
def set_description(self, description):
157100
self.description = description
158101

159102
def set_duration_ms(self, duration):
160103
self.duration = duration
161104

162-
def fail(self, reason):
105+
def set_status(self, status):
106+
self.status = status
107+
108+
def set_start_time(self, gmt_string):
109+
self.start_time = gmt_string
110+
111+
def fail(self, message):
163112
self.status = 'failed'
164113
ta = self.add_text_annotation('FAIL', 'error')
165-
ta.description = reason
114+
ta.description = message
166115

167-
def note_info(self, message):
116+
def add_info_annotation(self, message):
168117
ta = self.add_text_annotation('Info', 'info')
169118
ta.description = message
170119

171-
def note_warn(self, message):
120+
def add_warning_annotation(self, message):
172121
ta = self.add_text_annotation('Warning', 'warn')
173122
ta.description = message
174123

175-
def note_error(self, message):
124+
def add_error_annotation(self, message):
176125
ta = self.add_text_annotation('Error', 'error')
177126
ta.description = message
178127

@@ -181,26 +130,20 @@ def add_custom_data(self, name, value):
181130
self.custom_data.append(d)
182131
return d
183132

184-
def add_file_annotation(self, file_path, level='info', description='', mime_type='text/plain'):
185-
fa = FileAnnotation(file_path, level, description, mime_type)
133+
def add_file_annotation(self, name, level='info', description='',
134+
file_path=None, mime_type='text/plain'):
135+
fa = Annotation(name, level, description, file_path, mime_type)
186136
self.annotations.append(fa)
187137
return fa
188138

189139
def add_text_annotation(self, name, level='info', description=''):
190-
ta = TextAnnotation(name, level, description)
140+
ta = Annotation(name, level, description)
191141
self.annotations.append(ta)
192142
return ta
193143

194-
def set_status(self, status):
195-
self.status = status
196-
197-
def set_start_time(self, gmt_string):
198-
self._start_time = gmt_string
199-
200144

201145
class TestSuite:
202146
def __init__(self, name):
203-
204147
# optional sub-suites
205148
self.sub_suites = {}
206149
self.is_root_suite = False
@@ -211,27 +154,19 @@ def __init__(self, name):
211154
self.custom_data = []
212155
self.annotations = []
213156

214-
def _clean_up(self):
215-
for s in self.sub_suites:
216-
self.get_or_add_suite(s)._clean_up()
217-
for tc in self.test_cases:
218-
tc._clean_up()
219-
for a in self.annotations:
220-
a._clean_up()
221-
222157
def add_test_case(self, tc):
223158
self.test_cases.append(tc)
224159
return tc
225160

226-
def get_or_add_suite(self, suite_name):
161+
def get_or_add_test_suite(self, suite_name):
227162
if not suite_name:
228163
# write under root suite
229-
return self.get_or_add_suite('uncategorized')
164+
return self.get_or_add_test_suite('uncategorized')
230165
if suite_name in self.sub_suites.keys():
231166
return self.sub_suites[suite_name]
232-
return self.add_suite(suite_name)
167+
return self.add_test_suite(suite_name)
233168

234-
def add_suite(self, name):
169+
def add_test_suite(self, name):
235170
new_suite = TestSuite(name)
236171
self.sub_suites[str(name)] = new_suite
237172
return new_suite
@@ -241,51 +176,38 @@ def add_custom_data(self, name, value):
241176
self.custom_data.append(d)
242177
return d
243178

244-
def add_file_annotation(self, file_path, level='info', description='',
245-
mime_type='text/plain', delete_file=True):
246-
fa = FileAnnotation(file_path, level, description, mime_type, delete_file)
179+
def add_file_annotation(self, name, level='info', description='',
180+
file_path=None, mime_type='text/plain'):
181+
fa = Annotation(name, level, description, file_path, mime_type)
247182
self.annotations.append(fa)
248183
return fa
249184

250185
def add_text_annotation(self, name, level='info', description=''):
251-
ta = TextAnnotation(name, level, description)
186+
ta = Annotation(name, level, description)
252187
self.annotations.append(ta)
253188
return ta
254189

255190

256191
class XmlWriter:
257-
xmlTemplate = r'''
258-
<?xml-stylesheet type="text/xsl" xml version="1.0" encoding="UTF-8?>
259-
<reporter schema_version="1.0">
260-
</reporter>
261-
'''
262-
263192
def __init__(self, report):
264193
self.report = report
265-
self.dom = parseString(self.xmlTemplate)
194+
self.dom = parseString('<reporter schema_version="1.0"/>')
266195

267-
@staticmethod
268-
def fix_line_ends(s):
269-
br = '<br/>'
270-
ss = str(s)
271-
sss = ss.replace('\r\n', br)
272-
return sss.replace('\n', br)
273-
274-
def write(self, target_file_path=''):
196+
def write(self, target_file_path, to_pretty=False):
275197
doc_elem = self.dom.documentElement
276198
self._write_suite(doc_elem, self.report.get_root_suite())
277199
if target_file_path:
278200
with open(target_file_path, 'w') as f:
279-
f.write(self.dom.toprettyxml())
201+
if to_pretty:
202+
f.write(self.dom.toprettyxml())
203+
else:
204+
f.write(self.dom.toxml())
280205
f.flush()
281206
else:
282-
sys.stdout.write(self.dom.toxml())
283-
# print(self.dom.toprettyxml())
284-
# print(self.dom.toxml())
285-
self._clean_up()
286-
287-
def _clean_up(self):
288-
self.report.get_root_suite()._clean_up()
207+
if to_pretty:
208+
sys.stdout.write(self.dom.toprettyxml())
209+
else:
210+
sys.stdout.write(self.dom.toxml())
289211

290212
def _write_suite(self, parent_node, test_suite):
291213
# don't explicitly add suite for root suite
@@ -298,10 +220,10 @@ def _write_suite(self, parent_node, test_suite):
298220
parent_node.appendChild(suite_elem)
299221

300222
for a in test_suite.annotations:
301-
a._write_xml(suite_elem, self.dom)
223+
a.write_xml(suite_elem, self.dom)
302224

303225
for d in test_suite.custom_data:
304-
d._write_xml(suite_elem, self.dom)
226+
d.write_xml(suite_elem, self.dom)
305227

306228
for tc in test_suite.test_cases:
307229
self._write_test_case(suite_elem, tc)
@@ -314,17 +236,17 @@ def _write_test_case(self, parent_node, test_case):
314236
elem_tc = self.dom.createElement('test_case')
315237

316238
elem_tc.setAttribute('name', test_case.name)
317-
elem_tc.setAttribute('description', self.fix_line_ends(test_case.description))
239+
elem_tc.setAttribute('description', test_case.description)
318240
elem_tc.setAttribute('status', test_case.status)
319241
elem_tc.setAttribute('start_time', test_case.start_time)
320242
elem_tc.setAttribute('duration', str(test_case.duration))
321243
parent_node.appendChild(elem_tc)
322244

323245
for a in test_case.annotations:
324-
a._write_xml(elem_tc, self.dom)
246+
a.write_xml(elem_tc, self.dom)
325247

326248
for d in test_case.custom_data:
327-
d._write_xml(elem_tc, self.dom)
249+
d.write_xml(elem_tc, self.dom)
328250

329251

330252
class TestspaceReport(TestSuite):
@@ -335,10 +257,6 @@ def __init__(self):
335257
def get_root_suite(self):
336258
return self
337259

338-
def xml_file(self, outfile):
339-
writer = XmlWriter(self)
340-
writer.write(outfile)
341-
342-
def xml_console(self):
260+
def write_xml(self, outfile=None, to_pretty=False):
343261
writer = XmlWriter(self)
344-
writer.write()
262+
writer.write(outfile, to_pretty)

0 commit comments

Comments
 (0)