11from __future__ import print_function
22import base64
33import gzip
4- import ntpath
54import os
5+ import io
6+ from io import BytesIO
67import os .path
78import sys
8- import tempfile
99from 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
3535class 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
13989class 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
201145class 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
256191class 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
330252class 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