VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testdriver/reporter.py@ 94522

Last change on this file since 94522 was 94126, checked in by vboxsync, 3 years ago

ValKit/testdriver: pylint 2.9.6 adjustments (mostly about using 'with' statements).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 67.5 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: reporter.py 94126 2022-03-08 14:18:58Z vboxsync $
3# pylint: disable=too-many-lines
4
5"""
6Testdriver reporter module.
7"""
8
9from __future__ import print_function;
10
11__copyright__ = \
12"""
13Copyright (C) 2010-2022 Oracle Corporation
14
15This file is part of VirtualBox Open Source Edition (OSE), as
16available from http://www.virtualbox.org. This file is free software;
17you can redistribute it and/or modify it under the terms of the GNU
18General Public License (GPL) as published by the Free Software
19Foundation, in version 2 as it comes in the "COPYING" file of the
20VirtualBox OSE distribution. VirtualBox OSE is distributed in the
21hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
22
23The contents of this file may alternatively be used under the terms
24of the Common Development and Distribution License Version 1.0
25(CDDL) only, as it comes in the "COPYING.CDDL" file of the
26VirtualBox OSE distribution, in which case the provisions of the
27CDDL are applicable instead of those of the GPL.
28
29You may elect to license modified versions of this file under the
30terms and conditions of either the GPL or the CDDL or both.
31"""
32__version__ = "$Revision: 94126 $"
33
34
35# Standard Python imports.
36import array
37import datetime
38import errno
39import gc
40import os
41import os.path
42import sys
43import time
44import threading
45import traceback
46
47# Validation Kit imports.
48from common import utils;
49
50## test reporter instance
51g_oReporter = None # type: ReporterBase
52g_sReporterName = None;
53
54
55class ReporterLock(object):
56 """
57 Work around problem with garbage collection triggering __del__ method with
58 logging while inside the logger lock and causing a deadlock.
59 """
60
61 def __init__(self, sName):
62 self.sName = sName;
63 self.oLock = threading.RLock();
64 self.oOwner = None;
65 self.cRecursion = 0;
66 self.fRestoreGC = False;
67
68 def acquire(self):
69 """ Acquire the lock. """
70 oSelf = threading.current_thread();
71
72 # Take the lock.
73 if not self.oLock.acquire(): # pylint: disable=consider-using-with
74 return False;
75
76 self.oOwner = oSelf;
77 self.cRecursion += 1;
78
79 # Disable GC to avoid __del__ w/ log statement randomly reenter the logger.
80 if self.cRecursion == 1:
81 self.fRestoreGC = gc.isenabled();
82 if self.fRestoreGC:
83 gc.disable();
84
85 return True;
86
87 def release(self):
88 """ Release the lock. """
89 oSelf = threading.current_thread();
90
91 # Check the ownership.
92 if oSelf != self.oOwner:
93 raise threading.ThreadError();
94
95 # Drop one recursion.
96 self.cRecursion -= 1;
97 if self.cRecursion <= 0:
98
99 # Final recursion. Clear owner and re-enable GC.
100 self.oOwner = None;
101 if self.fRestoreGC:
102 self.fRestoreGC = False;
103 gc.enable();
104
105 self.oLock.release();
106
107## Reporter lock.
108g_oLock = ReporterLock('reporter');
109
110
111
112class PythonLoggingStream(object):
113 """
114 Python logging => testdriver/reporter.py stream.
115 """
116
117 def write(self, sText):
118 """Writes python log message to our stream."""
119 if g_oReporter is not None:
120 sText = sText.rstrip("\r\n");
121 #g_oReporter.log(0, 'python: %s' % (sText), utils.getCallerName(), utils.getTimePrefix());
122 return True;
123
124 def flush(self):
125 """Flushes the stream."""
126 return True;
127
128
129class ReporterBase(object):
130 """
131 Base class for the reporters.
132 """
133
134 def __init__(self):
135 self.iVerbose = 1;
136 self.iDebug = 0;
137 self.cErrors = 0;
138 self.fTimedOut = False; # Once set, it trickles all the way up.
139 self.atTests = [];
140 self.sName = os.path.splitext(os.path.basename(sys.argv[0]))[0];
141
142 # Hook into the python logging.
143 import logging;
144 logging.basicConfig(stream = PythonLoggingStream(),
145 level = logging.DEBUG,
146 format = '%(name)-12s %(levelname)-8s %(message)s');
147 #
148 # Introspection and configuration.
149 #
150
151 def isLocal(self):
152 """Is this a local reporter?"""
153 return False;
154
155 def incVerbosity(self):
156 """Increases the verbosity level."""
157 self.iVerbose += 1;
158
159 def incDebug(self):
160 """Increases the debug level."""
161 self.iDebug += 1;
162
163 def getVerbosity(self):
164 """Returns the current verbosity level."""
165 return self.iVerbose;
166
167 def getDebug(self):
168 """Returns the current debug level."""
169 return self.iDebug;
170
171 def appendToProcessName(self, sAppend):
172 """
173 Appends sAppend to the base process name.
174 Returns the new process name.
175 """
176 self.sName = os.path.splitext(os.path.basename(sys.argv[0]))[0] + sAppend;
177 return self.sName;
178
179
180 #
181 # Generic logging.
182 #
183
184 def log(self, iLevel, sText, sCaller, sTsPrf):
185 """
186 Writes the specfied text to the log if iLevel is less or requal
187 to iVerbose.
188 """
189 _ = iLevel; _ = sText; _ = sCaller; _ = sTsPrf;
190 return 0;
191
192 #
193 # XML output from the reporter.
194 #
195
196 def _xmlEscAttr(self, sValue):
197 """Escapes an XML attribute value."""
198 sValue = sValue.replace('&', '&amp;');
199 sValue = sValue.replace('<', '&lt;');
200 sValue = sValue.replace('>', '&gt;');
201 #sValue = sValue.replace('\'', '&apos;');
202 sValue = sValue.replace('"', '&quot;');
203 sValue = sValue.replace('\n', '&#xA');
204 sValue = sValue.replace('\r', '&#xD');
205 return sValue;
206
207 def _xmlWrite(self, asText, fIndent = True):
208 """XML output function for the reporter."""
209 _ = asText; _ = fIndent;
210 return None;
211
212 def xmlFlush(self, fRetry = False, fForce = False):
213 """Flushes XML output if buffered."""
214 _ = fRetry; _ = fForce;
215 return True;
216
217 #
218 # XML output from child.
219 #
220
221 def subXmlStart(self, oFileWrapper):
222 """Called by the file wrapper when the first bytes are written to the test pipe."""
223 _ = oFileWrapper;
224 return None;
225
226 def subXmlWrite(self, oFileWrapper, sRawXml, sCaller):
227 """Called by the file wrapper write method for test pipes."""
228 return self.log(0, 'raw xml%s: %s' % (oFileWrapper.sPrefix, sRawXml), sCaller, utils.getTimePrefix());
229
230 def subXmlEnd(self, oFileWrapper):
231 """Called by the file wrapper __del__ method for test pipes."""
232 _ = oFileWrapper;
233 return None;
234
235 #
236 # File output.
237 #
238
239 def addLogFile(self, oSrcFile, sSrcFilename, sAltName, sDescription, sKind, sCaller, sTsPrf):
240 """
241 Adds the file to the report.
242 Returns True on success, False on failure.
243 """
244 _ = oSrcFile; _ = sSrcFilename; _ = sAltName; _ = sDescription; _ = sKind; _ = sCaller; _ = sTsPrf;
245 return True;
246
247 def addLogString(self, sLog, sLogName, sDescription, sKind, sCaller, sTsPrf):
248 """
249 Adds the file to the report.
250 Returns True on success, False on failure.
251 """
252 _ = sLog; _ = sLogName; _ = sDescription; _ = sKind; _ = sCaller; _ = sTsPrf;
253 return True;
254
255 #
256 # Test reporting
257 #
258
259 def _testGetFullName(self):
260 """
261 Mangles the test names in atTest into a single name to make it easier
262 to spot where we are.
263 """
264 sName = '';
265 for t in self.atTests:
266 if sName != '':
267 sName += ', ';
268 sName += t[0];
269 return sName;
270
271 def testIncErrors(self):
272 """Increates the error count."""
273 self.cErrors += 1;
274 return self.cErrors;
275
276 def testSetTimedOut(self):
277 """Sets time out indicator for the current test and increases the error counter."""
278 self.fTimedOut = True;
279 self.cErrors += 1;
280 return None;
281
282 def testStart(self, sName, sCaller):
283 """ Starts a new test, may be nested. """
284 (sTsPrf, sTsIso) = utils.getTimePrefixAndIsoTimestamp();
285 self._xmlWrite([ '<Test timestamp="%s" name="%s">' % (sTsIso, self._xmlEscAttr(sName),), ]);
286 self.atTests.append((sName, self.cErrors, self.fTimedOut));
287 self.fTimedOut = False;
288 return self.log(1, ' %-50s: TESTING' % (self._testGetFullName()), sCaller, sTsPrf);
289
290 def testValue(self, sName, sValue, sUnit, sCaller):
291 """ Reports a benchmark value or something simiarlly useful. """
292 (sTsPrf, sTsIso) = utils.getTimePrefixAndIsoTimestamp();
293 self._xmlWrite([ '<Value timestamp="%s" name="%s" unit="%s" value="%s"/>'
294 % (sTsIso, self._xmlEscAttr(sName), self._xmlEscAttr(sUnit), self._xmlEscAttr(sValue)), ]);
295 return self.log(0, '** %-48s: %12s %s' % (sName, sValue, sUnit), sCaller, sTsPrf);
296
297 def testFailure(self, sDetails, sCaller):
298 """ Reports a failure. """
299 (sTsPrf, sTsIso) = utils.getTimePrefixAndIsoTimestamp();
300 self.cErrors = self.cErrors + 1;
301 self._xmlWrite([ '<FailureDetails timestamp="%s" text="%s"/>' % (sTsIso, self._xmlEscAttr(sDetails),), ]);
302 return self.log(0, sDetails, sCaller, sTsPrf);
303
304 def testDone(self, fSkipped, sCaller):
305 """
306 Marks the current test as DONE, pops it and maks the next test on the
307 stack current.
308 Returns (name, errors).
309 """
310 (sTsPrf, sTsIso) = utils.getTimePrefixAndIsoTimestamp();
311 sFullName = self._testGetFullName();
312
313 # safe pop
314 if not self.atTests:
315 self.log(0, 'testDone on empty test stack!', sCaller, sTsPrf);
316 return ('internal error', 0);
317 fTimedOut = self.fTimedOut;
318 sName, cErrorsStart, self.fTimedOut = self.atTests.pop();
319
320 # log + xml.
321 cErrors = self.cErrors - cErrorsStart;
322 if cErrors == 0:
323 if fSkipped is not True:
324 self._xmlWrite([ ' <Passed timestamp="%s"/>' % (sTsIso,), '</Test>' ],);
325 self.log(1, '** %-50s: PASSED' % (sFullName,), sCaller, sTsPrf);
326 else:
327 self._xmlWrite([ ' <Skipped timestamp="%s"/>' % (sTsIso,), '</Test>' ]);
328 self.log(1, '** %-50s: SKIPPED' % (sFullName,), sCaller, sTsPrf);
329 elif fTimedOut:
330 self._xmlWrite([ ' <TimedOut timestamp="%s" errors="%d"/>' % (sTsIso, cErrors), '</Test>' ]);
331 self.log(0, '** %-50s: TIMED-OUT - %d errors' % (sFullName, cErrors), sCaller, sTsPrf);
332 else:
333 self._xmlWrite([ ' <Failed timestamp="%s" errors="%d"/>' % (sTsIso, cErrors), '</Test>' ]);
334 self.log(0, '** %-50s: FAILED - %d errors' % (sFullName, cErrors), sCaller, sTsPrf);
335
336 # Flush buffers when reaching the last test.
337 if not self.atTests:
338 self.xmlFlush(fRetry = True);
339
340 return (sName, cErrors);
341
342 def testErrorCount(self):
343 """
344 Returns the number of errors accumulated by the current test.
345 """
346 cTests = len(self.atTests);
347 if cTests <= 0:
348 return self.cErrors;
349 return self.cErrors - self.atTests[cTests - 1][1];
350
351 def testCleanup(self, sCaller):
352 """
353 Closes all open test as failed.
354 Returns True if no open tests, False if there were open tests.
355 """
356 if not self.atTests:
357 return True;
358 for _ in range(len(self.atTests)):
359 self.testFailure('Test not closed by test drver', sCaller)
360 self.testDone(False, sCaller);
361 return False;
362
363 #
364 # Misc.
365 #
366
367 def doPollWork(self, sDebug = None):
368 """
369 Check if any pending stuff expired and needs doing.
370 """
371 _ = sDebug;
372 return None;
373
374
375
376
377class LocalReporter(ReporterBase):
378 """
379 Local reporter instance.
380 """
381
382 def __init__(self):
383 ReporterBase.__init__(self);
384 self.oLogFile = None;
385 self.oXmlFile = None;
386 self.fXmlOk = True;
387 self.iSubXml = 0;
388 self.iOtherFile = 0;
389 self.fnGetIsoTimestamp = utils.getIsoTimestamp; # Hack to get a timestamp in __del__.
390 self.oStdErr = sys.stderr; # Hack for __del__ output.
391
392 #
393 # Figure the main log directory.
394 #
395 try:
396 self.sDefLogDir = os.path.abspath(os.path.expanduser(os.path.join('~', 'VBoxTestLogs')));
397 except:
398 self.sDefLogDir = os.path.abspath("VBoxTestLogs");
399 try:
400 sLogDir = os.path.abspath(os.environ.get('TESTBOX_REPORTER_LOG_DIR', self.sDefLogDir));
401 if not os.path.isdir(sLogDir):
402 os.makedirs(sLogDir, 0o750);
403 except:
404 sLogDir = self.sDefLogDir;
405 if not os.path.isdir(sLogDir):
406 os.makedirs(sLogDir, 0o750);
407
408 #
409 # Make a subdirectory for this test run.
410 #
411 sTs = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H-%M-%S.log');
412 self.sLogDir = sLogDir = os.path.join(sLogDir, '%s-%s' % (sTs, self.sName));
413 try:
414 os.makedirs(self.sLogDir, 0o750);
415 except:
416 self.sLogDir = '%s-%s' % (self.sLogDir, os.getpid());
417 os.makedirs(self.sLogDir, 0o750);
418
419 #
420 # Open the log file and write a header.
421 #
422 sLogName = os.path.join(self.sLogDir, 'testsuite.log');
423 sTsIso = utils.getIsoTimestamp();
424 if sys.version_info[0] >= 3: # Add 'b' to prevent write taking issue with encode('utf-8') not returning a string.
425 self.oLogFile = utils.openNoInherit(sLogName, "wb");
426 else:
427 self.oLogFile = utils.openNoInherit(sLogName, "w");
428 self.oLogFile.write(('Created log file at %s.\nRunning: %s' % (sTsIso, sys.argv)).encode('utf-8'));
429
430 #
431 # Open the xml log file and write the mandatory introduction.
432 #
433 # Note! This is done here and not in the base class because the remote
434 # logger doesn't really need this. It doesn't need the outer
435 # test wrapper either.
436 #
437 sXmlName = os.path.join(self.sLogDir, 'testsuite.xml');
438 if sys.version_info[0] >= 3: # Add 'b' to prevent write taking issue with encode('utf-8') not returning a string.
439 self.oXmlFile = utils.openNoInherit(sXmlName, "wb");
440 else:
441 self.oXmlFile = utils.openNoInherit(sXmlName, "w");
442 self._xmlWrite([ '<?xml version="1.0" encoding="UTF-8" ?>',
443 '<Test timestamp="%s" name="%s">' % (sTsIso, self._xmlEscAttr(self.sName),), ],
444 fIndent = False);
445
446 def __del__(self):
447 """Ends and completes the log files."""
448 try: sTsIso = self.fnGetIsoTimestamp();
449 except Exception as oXcpt:
450 sTsIso = str(oXcpt);
451
452 if self.oLogFile is not None:
453 try:
454 self.oLogFile.write(('\nThe End %s\n' % (sTsIso,)).encode('utf-8'));
455 self.oLogFile.close();
456 except: pass;
457 self.oLogFile = None;
458
459 if self.oXmlFile is not None:
460 self._closeXml(sTsIso);
461 self.oXmlFile = None;
462
463 def _closeXml(self, sTsIso):
464 """Closes the XML file."""
465 if self.oXmlFile is not None:
466 # pop the test stack
467 while self.atTests:
468 sName, cErrorsStart, self.fTimedOut = self.atTests.pop();
469 self._xmlWrite([ '<End timestamp="%s" errors="%d"/>' % (sTsIso, self.cErrors - cErrorsStart,),
470 '</%s>' % (sName,), ]);
471
472 # The outer one is not on the stack.
473 self._xmlWrite([ ' <End timestamp="%s"/>' % (sTsIso,),
474 '</Test>', ], fIndent = False);
475 try:
476 self.oXmlFile.close();
477 self.oXmlFile = None;
478 except:
479 pass;
480
481 def _xmlWrite(self, asText, fIndent = True):
482 """Writes to the XML file."""
483 for sText in asText:
484 if fIndent:
485 sIndent = ''.ljust((len(self.atTests) + 1) * 2);
486 sText = sIndent + sText;
487 sText += '\n';
488
489 try:
490 self.oXmlFile.write(sText.encode('utf-8'));
491 except:
492 if self.fXmlOk:
493 traceback.print_exc();
494 self.fXmlOk = False;
495 return False;
496 return True;
497
498 #
499 # Overridden methods.
500 #
501
502 def isLocal(self):
503 """Is this a local reporter?"""
504 return True;
505
506 def log(self, iLevel, sText, sCaller, sTsPrf):
507 if iLevel <= self.iVerbose:
508 # format it.
509 if self.iDebug <= 0:
510 sLogText = '%s %s' % (sTsPrf, sText);
511 elif self.iDebug <= 1:
512 sLogText = '%s %30s: %s' % (sTsPrf, sCaller, sText);
513 else:
514 sLogText = '%s e=%u %30s: %s' % (sTsPrf, self.cErrors, sCaller, sText);
515
516 # output it.
517 if sys.version_info[0] >= 3:
518 sAscii = sLogText;
519 else:
520 sAscii = sLogText.encode('ascii', 'replace');
521 if self.iDebug == 0:
522 print('%s: %s' % (self.sName, sAscii), file = self.oStdErr);
523 else:
524 print('%s' % (sAscii), file = self.oStdErr);
525 sLogText += '\n';
526 try:
527 self.oLogFile.write(sLogText.encode('utf-8'));
528 except:
529 pass;
530 return 0;
531
532 def addLogFile(self, oSrcFile, sSrcFilename, sAltName, sDescription, sKind, sCaller, sTsPrf):
533 # Figure the destination filename.
534 iOtherFile = self.iOtherFile;
535 self.iOtherFile += 1;
536 sDstFilename = os.path.join(self.sLogDir, 'other-%d-%s.log' \
537 % (iOtherFile, os.path.splitext(os.path.basename(sSrcFilename))[0]));
538 self.log(0, '** Other log file: %s - %s (%s)' % (sDstFilename, sDescription, sSrcFilename), sCaller, sTsPrf);
539
540 # Open the destination file and copy over the data.
541 fRc = True;
542 try:
543 oDstFile = utils.openNoInherit(sDstFilename, 'wb');
544 except Exception as oXcpt:
545 self.log(0, 'error opening %s: %s' % (sDstFilename, oXcpt), sCaller, sTsPrf);
546 else:
547 while True:
548 try:
549 abBuf = oSrcFile.read(65536);
550 except Exception as oXcpt:
551 fRc = False;
552 self.log(0, 'error reading %s: %s' % (sSrcFilename, oXcpt), sCaller, sTsPrf);
553 else:
554 try:
555 oDstFile.write(abBuf);
556 except Exception as oXcpt:
557 fRc = False;
558 self.log(0, 'error writing %s: %s' % (sDstFilename, oXcpt), sCaller, sTsPrf);
559 else:
560 if abBuf:
561 continue;
562 break;
563 oDstFile.close();
564
565 # Leave a mark in the XML log.
566 self._xmlWrite(['<LogFile timestamp="%s" filename="%s" source="%s" kind="%s" ok="%s">%s</LogFile>\n'
567 % (utils.getIsoTimestamp(), self._xmlEscAttr(os.path.basename(sDstFilename)), self._xmlEscAttr(sSrcFilename), \
568 self._xmlEscAttr(sKind), fRc, self._xmlEscAttr(sDescription))] );
569 _ = sAltName;
570 return fRc;
571
572 def addLogString(self, sLog, sLogName, sDescription, sKind, sCaller, sTsPrf):
573 # Figure the destination filename.
574 iOtherFile = self.iOtherFile;
575 self.iOtherFile += 1;
576 sDstFilename = os.path.join(self.sLogDir, 'other-%d-%s.log' \
577 % (iOtherFile, os.path.splitext(os.path.basename(sLogName))[0]));
578 self.log(0, '** Other log file: %s - %s (%s)' % (sDstFilename, sDescription, sLogName), sCaller, sTsPrf);
579
580 # Open the destination file and copy over the data.
581 fRc = True;
582 try:
583 oDstFile = utils.openNoInherit(sDstFilename, 'w');
584 except Exception as oXcpt:
585 self.log(0, 'error opening %s: %s' % (sDstFilename, oXcpt), sCaller, sTsPrf);
586 else:
587 try:
588 oDstFile.write(sLog);
589 except Exception as oXcpt:
590 fRc = False;
591 self.log(0, 'error writing %s: %s' % (sDstFilename, oXcpt), sCaller, sTsPrf);
592
593 oDstFile.close();
594
595 # Leave a mark in the XML log.
596 self._xmlWrite(['<LogFile timestamp="%s" filename="%s" source="%s" kind="%s" ok="%s">%s</LogFile>\n'
597 % (utils.getIsoTimestamp(), self._xmlEscAttr(os.path.basename(sDstFilename)), self._xmlEscAttr(sLogName), \
598 self._xmlEscAttr(sKind), fRc, self._xmlEscAttr(sDescription))] );
599 return fRc;
600
601 def subXmlStart(self, oFileWrapper):
602 # Open a new file and just include it from the main XML.
603 iSubXml = self.iSubXml;
604 self.iSubXml += 1;
605 sSubXmlName = os.path.join(self.sLogDir, 'sub-%d.xml' % (iSubXml,));
606 try:
607 oFileWrapper.oSubXmlFile = utils.openNoInherit(sSubXmlName, "w");
608 except:
609 errorXcpt('open(%s)' % oFileWrapper.oSubXmlName);
610 oFileWrapper.oSubXmlFile = None;
611 else:
612 self._xmlWrite(['<Include timestamp="%s" filename="%s"/>\n'
613 % (utils.getIsoTimestamp(), self._xmlEscAttr(os.path.basename(sSubXmlName)))]);
614 return None;
615
616 def subXmlWrite(self, oFileWrapper, sRawXml, sCaller):
617 if oFileWrapper.oSubXmlFile is not None:
618 try:
619 oFileWrapper.oSubXmlFile.write(sRawXml);
620 except:
621 pass;
622 if sCaller is None: pass; # pychecker - NOREF
623 return None;
624
625 def subXmlEnd(self, oFileWrapper):
626 if oFileWrapper.oSubXmlFile is not None:
627 try:
628 oFileWrapper.oSubXmlFile.close();
629 oFileWrapper.oSubXmlFile = None;
630 except:
631 pass;
632 return None;
633
634
635
636class RemoteReporter(ReporterBase):
637 """
638 Reporter that talks to the test manager server.
639 """
640
641
642 ## The XML sync min time (seconds).
643 kcSecXmlFlushMin = 30;
644 ## The XML sync max time (seconds).
645 kcSecXmlFlushMax = 120;
646 ## The XML sync idle time before flushing (seconds).
647 kcSecXmlFlushIdle = 5;
648 ## The XML sync line count threshold.
649 kcLinesXmlFlush = 512;
650
651 ## The retry timeout.
652 kcSecTestManagerRetryTimeout = 120;
653 ## The request timeout.
654 kcSecTestManagerRequestTimeout = 30;
655
656
657 def __init__(self):
658 ReporterBase.__init__(self);
659 self.sTestManagerUrl = os.environ.get('TESTBOX_MANAGER_URL');
660 self.sTestBoxUuid = os.environ.get('TESTBOX_UUID');
661 self.idTestBox = int(os.environ.get('TESTBOX_ID'));
662 self.idTestSet = int(os.environ.get('TESTBOX_TEST_SET_ID'));
663 self._asXml = [];
664 self._secTsXmlFlush = utils.timestampSecond();
665 self._secTsXmlLast = self._secTsXmlFlush;
666 self._fXmlFlushing = False;
667 self.oOutput = sys.stdout; # Hack for __del__ output.
668 self.fFlushEachLine = True;
669 self.fDebugXml = 'TESTDRIVER_REPORTER_DEBUG_XML' in os.environ;
670
671 # Prepare the TM connecting.
672 from common import constants;
673 if sys.version_info[0] >= 3:
674 import urllib;
675 self._fnUrlEncode = urllib.parse.urlencode; # pylint: disable=no-member
676 self._fnUrlParseQs = urllib.parse.parse_qs; # pylint: disable=no-member
677 self._oParsedTmUrl = urllib.parse.urlparse(self.sTestManagerUrl); # pylint: disable=no-member
678 import http.client as httplib; # pylint: disable=no-name-in-module,import-error
679 else:
680 import urllib;
681 self._fnUrlEncode = urllib.urlencode; # pylint: disable=no-member
682 import urlparse; # pylint: disable=import-error
683 self._fnUrlParseQs = urlparse.parse_qs; # pylint: disable=no-member
684 self._oParsedTmUrl = urlparse.urlparse(self.sTestManagerUrl); # pylint: disable=no-member
685 import httplib; # pylint: disable=no-name-in-module,import-error
686
687 if sys.version_info[0] >= 3 \
688 or (sys.version_info[0] == 2 and sys.version_info[1] >= 6):
689 if self._oParsedTmUrl.scheme == 'https': # pylint: disable=no-member
690 self._fnTmConnect = lambda: httplib.HTTPSConnection(self._oParsedTmUrl.hostname,
691 timeout = self.kcSecTestManagerRequestTimeout);
692 else:
693 self._fnTmConnect = lambda: httplib.HTTPConnection( self._oParsedTmUrl.hostname,
694 timeout = self.kcSecTestManagerRequestTimeout);
695 else:
696 if self._oParsedTmUrl.scheme == 'https': # pylint: disable=no-member
697 self._fnTmConnect = lambda: httplib.HTTPSConnection(self._oParsedTmUrl.hostname);
698 else:
699 self._fnTmConnect = lambda: httplib.HTTPConnection( self._oParsedTmUrl.hostname);
700 self._dHttpHeader = \
701 {
702 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
703 'User-Agent': 'TestDriverReporter/%s.0 (%s, %s)' % (__version__, utils.getHostOs(), utils.getHostArch(),),
704 'Accept': 'text/plain,application/x-www-form-urlencoded',
705 'Accept-Encoding': 'identity',
706 'Cache-Control': 'max-age=0',
707 #'Connection': 'keep-alive',
708 };
709
710 dParams = {
711 constants.tbreq.ALL_PARAM_TESTBOX_UUID: self.sTestBoxUuid,
712 constants.tbreq.ALL_PARAM_TESTBOX_ID: self.idTestBox,
713 constants.tbreq.RESULT_PARAM_TEST_SET_ID: self.idTestSet,
714 };
715 self._sTmServerPath = '/%s/testboxdisp.py?%s' \
716 % ( self._oParsedTmUrl.path.strip('/'), # pylint: disable=no-member
717 self._fnUrlEncode(dParams), );
718
719 def __del__(self):
720 """Flush pending log messages?"""
721 if self._asXml:
722 self._xmlDoFlush(self._asXml, fRetry = True, fDtor = True);
723
724 def _writeOutput(self, sText):
725 """ Does the actual writing and flushing. """
726 if sys.version_info[0] >= 3:
727 print(sText, file = self.oOutput);
728 else:
729 print(sText.encode('ascii', 'replace'), file = self.oOutput);
730 if self.fFlushEachLine: self.oOutput.flush();
731 return None;
732
733 #
734 # Talking to TM.
735 #
736
737 def _processTmStatusResponse(self, oConn, sOperation, fClose = True):
738 """
739 Processes HTTP reponse from the test manager.
740 Returns True, False or None. None should be retried, the others not.
741 May raise exception on HTTP issue (retry ok).
742 """
743 if sys.version_info[0] >= 3: import http.client as httplib; # pylint: disable=no-name-in-module,import-error
744 else: import httplib; # pylint: disable=import-error
745 from common import constants;
746
747 # Read the response and (optionally) close the connection.
748 oResponse = oConn.getresponse();
749 try:
750 sRspBody = oResponse.read();
751 except httplib.IncompleteRead as oXcpt:
752 self._writeOutput('%s: %s: Warning: httplib.IncompleteRead: %s [expected %s, got %s]'
753 % (utils.getTimePrefix(), sOperation, oXcpt, oXcpt.expected, len(oXcpt.partial),));
754 sRspBody = oXcpt.partial;
755 if fClose is True:
756 try: oConn.close();
757 except: pass;
758
759 # Make sure it's a string which encoding we grok.
760 if hasattr(sRspBody, 'decode'):
761 sRspBody = sRspBody.decode('utf-8', 'ignore');
762
763 # Check the content type.
764 sContentType = oResponse.getheader('Content-Type');
765 if sContentType is not None and sContentType == 'application/x-www-form-urlencoded; charset=utf-8':
766
767 # Parse the body and check the RESULT parameter.
768 dResponse = self._fnUrlParseQs(sRspBody, strict_parsing = True);
769 sResult = dResponse.get(constants.tbresp.ALL_PARAM_RESULT, None);
770 if isinstance(sResult, list):
771 sResult = sResult[0] if len(sResult) == 1 else '%d results' % (len(sResult),);
772
773 if sResult is not None:
774 if sResult == constants.tbresp.STATUS_ACK:
775 return True;
776 if sResult == constants.tbresp.STATUS_NACK:
777 self._writeOutput('%s: %s: Failed (%s). (dResponse=%s)'
778 % (utils.getTimePrefix(), sOperation, sResult, dResponse,));
779 return False;
780
781 self._writeOutput('%s: %s: Failed - dResponse=%s' % (utils.getTimePrefix(), sOperation, dResponse,));
782 else:
783 self._writeOutput('%s: %s: Unexpected Content-Type: %s' % (utils.getTimePrefix(), sOperation, sContentType,));
784 self._writeOutput('%s: %s: Body: %s' % (utils.getTimePrefix(), sOperation, sRspBody,));
785 return None;
786
787 def _doUploadFile(self, oSrcFile, sSrcFilename, sDescription, sKind, sMime):
788 """ Uploads the given file to the test manager. """
789
790 # Prepare header and url.
791 dHeader = dict(self._dHttpHeader);
792 dHeader['Content-Type'] = 'application/octet-stream';
793 self._writeOutput('%s: _doUploadFile: sHeader=%s' % (utils.getTimePrefix(), dHeader,));
794 oSrcFile.seek(0, 2);
795 cbFileSize = oSrcFile.tell();
796 self._writeOutput('%s: _doUploadFile: size=%d' % (utils.getTimePrefix(), cbFileSize,));
797 oSrcFile.seek(0);
798
799 if cbFileSize <= 0: # The Test Manager will bitch if the file size is 0, so skip uploading.
800 self._writeOutput('%s: _doUploadFile: Empty file, skipping upload' % utils.getTimePrefix());
801 return False;
802
803 from common import constants;
804 sUrl = self._sTmServerPath + '&' \
805 + self._fnUrlEncode({ constants.tbreq.UPLOAD_PARAM_NAME: os.path.basename(sSrcFilename),
806 constants.tbreq.UPLOAD_PARAM_DESC: sDescription,
807 constants.tbreq.UPLOAD_PARAM_KIND: sKind,
808 constants.tbreq.UPLOAD_PARAM_MIME: sMime,
809 constants.tbreq.ALL_PARAM_ACTION: constants.tbreq.UPLOAD,
810 });
811
812 # Retry loop.
813 secStart = utils.timestampSecond();
814 while True:
815 try:
816 oConn = self._fnTmConnect();
817 oConn.request('POST', sUrl, oSrcFile.read(), dHeader);
818 fRc = self._processTmStatusResponse(oConn, '_doUploadFile', fClose = True);
819 oConn.close();
820 if fRc is not None:
821 return fRc;
822 except:
823 logXcpt('warning: exception during UPLOAD request');
824
825 if utils.timestampSecond() - secStart >= self.kcSecTestManagerRetryTimeout:
826 self._writeOutput('%s: _doUploadFile: Timed out.' % (utils.getTimePrefix(),));
827 break;
828 try: oSrcFile.seek(0);
829 except:
830 logXcpt();
831 break;
832 self._writeOutput('%s: _doUploadFile: Retrying...' % (utils.getTimePrefix(), ));
833 time.sleep(2);
834
835 return False;
836
837 def _doUploadString(self, sSrc, sSrcName, sDescription, sKind, sMime):
838 """ Uploads the given string as a separate file to the test manager. """
839
840 # Prepare header and url.
841 dHeader = dict(self._dHttpHeader);
842 dHeader['Content-Type'] = 'application/octet-stream';
843 self._writeOutput('%s: _doUploadString: sHeader=%s' % (utils.getTimePrefix(), dHeader,));
844 self._writeOutput('%s: _doUploadString: size=%d' % (utils.getTimePrefix(), sys.getsizeof(sSrc),));
845
846 from common import constants;
847 sUrl = self._sTmServerPath + '&' \
848 + self._fnUrlEncode({ constants.tbreq.UPLOAD_PARAM_NAME: os.path.basename(sSrcName),
849 constants.tbreq.UPLOAD_PARAM_DESC: sDescription,
850 constants.tbreq.UPLOAD_PARAM_KIND: sKind,
851 constants.tbreq.UPLOAD_PARAM_MIME: sMime,
852 constants.tbreq.ALL_PARAM_ACTION: constants.tbreq.UPLOAD,
853 });
854
855 # Retry loop.
856 secStart = utils.timestampSecond();
857 while True:
858 try:
859 oConn = self._fnTmConnect();
860 oConn.request('POST', sUrl, sSrc, dHeader);
861 fRc = self._processTmStatusResponse(oConn, '_doUploadString', fClose = True);
862 oConn.close();
863 if fRc is not None:
864 return fRc;
865 except:
866 logXcpt('warning: exception during UPLOAD request');
867
868 if utils.timestampSecond() - secStart >= self.kcSecTestManagerRetryTimeout:
869 self._writeOutput('%s: _doUploadString: Timed out.' % (utils.getTimePrefix(),));
870 break;
871 self._writeOutput('%s: _doUploadString: Retrying...' % (utils.getTimePrefix(), ));
872 time.sleep(2);
873
874 return False;
875
876 def _xmlDoFlush(self, asXml, fRetry = False, fDtor = False):
877 """
878 The code that does the actual talking to the server.
879 Used by both xmlFlush and __del__.
880 """
881 secStart = utils.timestampSecond();
882 while True:
883 fRc = None;
884 try:
885 # Post.
886 from common import constants;
887 sPostBody = self._fnUrlEncode({constants.tbreq.XML_RESULT_PARAM_BODY: '\n'.join(asXml),});
888 oConn = self._fnTmConnect();
889 oConn.request('POST',
890 self._sTmServerPath + ('&%s=%s' % (constants.tbreq.ALL_PARAM_ACTION, constants.tbreq.XML_RESULTS)),
891 sPostBody,
892 self._dHttpHeader);
893
894 fRc = self._processTmStatusResponse(oConn, '_xmlDoFlush', fClose = True);
895 if fRc is True:
896 if self.fDebugXml:
897 self._writeOutput('_xmlDoFlush:\n%s' % ('\n'.join(asXml),));
898 return (None, False);
899 if fRc is False:
900 self._writeOutput('_xmlDoFlush: Failed - we should abort the test, really.');
901 return (None, True);
902 except Exception as oXcpt:
903 if not fDtor:
904 logXcpt('warning: exception during XML_RESULTS request');
905 else:
906 self._writeOutput('warning: exception during XML_RESULTS request: %s' % (oXcpt,));
907
908 if fRetry is not True \
909 or utils.timestampSecond() - secStart >= self.kcSecTestManagerRetryTimeout:
910 break;
911 time.sleep(2);
912
913 return (asXml, False);
914
915
916 #
917 # Overridden methods.
918 #
919
920 def isLocal(self):
921 return False;
922
923 def log(self, iLevel, sText, sCaller, sTsPrf):
924 if iLevel <= self.iVerbose:
925 if self.iDebug <= 0:
926 sLogText = '%s %s' % (sTsPrf, sText);
927 elif self.iDebug <= 1:
928 sLogText = '%s %30s: %s' % (sTsPrf, sCaller, sText);
929 else:
930 sLogText = '%s e=%u %30s: %s' % (sTsPrf, self.cErrors, sCaller, sText);
931 self._writeOutput(sLogText);
932 return 0;
933
934 def addLogFile(self, oSrcFile, sSrcFilename, sAltName, sDescription, sKind, sCaller, sTsPrf):
935 fRc = True;
936 if sKind in [ 'text', 'log', 'process'] \
937 or sKind.startswith('log/') \
938 or sKind.startswith('info/') \
939 or sKind.startswith('process/'):
940 self.log(0, '*** Uploading "%s" - KIND: "%s" - DESC: "%s" ***'
941 % (sSrcFilename, sKind, sDescription), sCaller, sTsPrf);
942 self.xmlFlush();
943 g_oLock.release();
944 try:
945 self._doUploadFile(oSrcFile, sAltName, sDescription, sKind, 'text/plain');
946 finally:
947 g_oLock.acquire();
948 elif sKind.startswith('screenshot/'):
949 self.log(0, '*** Uploading "%s" - KIND: "%s" - DESC: "%s" ***'
950 % (sSrcFilename, sKind, sDescription), sCaller, sTsPrf);
951 self.xmlFlush();
952 g_oLock.release();
953 try:
954 self._doUploadFile(oSrcFile, sAltName, sDescription, sKind, 'image/png');
955 finally:
956 g_oLock.acquire();
957 elif sKind.startswith('misc/'):
958 self.log(0, '*** Uploading "%s" - KIND: "%s" - DESC: "%s" ***'
959 % (sSrcFilename, sKind, sDescription), sCaller, sTsPrf);
960 self.xmlFlush();
961 g_oLock.release();
962 try:
963 self._doUploadFile(oSrcFile, sAltName, sDescription, sKind, 'application/octet-stream');
964 finally:
965 g_oLock.acquire();
966 else:
967 self.log(0, '*** UNKNOWN FILE "%s" - KIND "%s" - DESC "%s" ***'
968 % (sSrcFilename, sKind, sDescription), sCaller, sTsPrf);
969 return fRc;
970
971 def addLogString(self, sLog, sLogName, sDescription, sKind, sCaller, sTsPrf):
972 fRc = True;
973 if sKind in [ 'text', 'log', 'process'] \
974 or sKind.startswith('log/') \
975 or sKind.startswith('info/') \
976 or sKind.startswith('process/'):
977 self.log(0, '*** Uploading "%s" - KIND: "%s" - DESC: "%s" ***'
978 % (sLogName, sKind, sDescription), sCaller, sTsPrf);
979 self.xmlFlush();
980 g_oLock.release();
981 try:
982 self._doUploadString(sLog, sLogName, sDescription, sKind, 'text/plain');
983 finally:
984 g_oLock.acquire();
985 else:
986 self.log(0, '*** UNKNOWN FILE "%s" - KIND "%s" - DESC "%s" ***'
987 % (sLogName, sKind, sDescription), sCaller, sTsPrf);
988 return fRc;
989
990 def xmlFlush(self, fRetry = False, fForce = False):
991 """
992 Flushes the XML back log. Called with the lock held, may leave it
993 while communicating with the server.
994 """
995 if not self._fXmlFlushing:
996 asXml = self._asXml;
997 self._asXml = [];
998 if asXml or fForce is True:
999 self._fXmlFlushing = True;
1000
1001 g_oLock.release();
1002 try:
1003 (asXml, fIncErrors) = self._xmlDoFlush(asXml, fRetry = fRetry);
1004 finally:
1005 g_oLock.acquire();
1006
1007 if fIncErrors:
1008 self.testIncErrors();
1009
1010 self._fXmlFlushing = False;
1011 if asXml is None:
1012 self._secTsXmlFlush = utils.timestampSecond();
1013 else:
1014 self._asXml = asXml + self._asXml;
1015 return True;
1016
1017 self._secTsXmlFlush = utils.timestampSecond();
1018 return False;
1019
1020 def _xmlFlushIfNecessary(self, fPolling = False, sDebug = None):
1021 """Flushes the XML back log if necessary."""
1022 tsNow = utils.timestampSecond();
1023 cSecs = tsNow - self._secTsXmlFlush;
1024 cSecsLast = tsNow - self._secTsXmlLast;
1025 if fPolling is not True:
1026 self._secTsXmlLast = tsNow;
1027
1028 # Absolute flush thresholds.
1029 if cSecs >= self.kcSecXmlFlushMax:
1030 return self.xmlFlush();
1031 if len(self._asXml) >= self.kcLinesXmlFlush:
1032 return self.xmlFlush();
1033
1034 # Flush if idle long enough.
1035 if cSecs >= self.kcSecXmlFlushMin \
1036 and cSecsLast >= self.kcSecXmlFlushIdle:
1037 return self.xmlFlush();
1038
1039 _ = sDebug;
1040 return False;
1041
1042 def _xmlWrite(self, asText, fIndent = True):
1043 """XML output function for the reporter."""
1044 self._asXml += asText;
1045 self._xmlFlushIfNecessary();
1046 _ = fIndent; # No pretty printing, thank you.
1047 return None;
1048
1049 def subXmlStart(self, oFileWrapper):
1050 oFileWrapper.sXmlBuffer = '';
1051 return None;
1052
1053 def subXmlWrite(self, oFileWrapper, sRawXml, sCaller):
1054 oFileWrapper.sXmlBuffer += sRawXml;
1055 _ = sCaller;
1056 return None;
1057
1058 def subXmlEnd(self, oFileWrapper):
1059 sRawXml = oFileWrapper.sXmlBuffer;
1060 ## @todo should validate the document here and maybe auto terminate things. Adding some hints to have the server do
1061 # this instead.
1062 g_oLock.acquire();
1063 try:
1064 self._asXml += [ '<PushHint testdepth="%d"/>' % (len(self.atTests),),
1065 sRawXml,
1066 '<PopHint testdepth="%d"/>' % (len(self.atTests),),];
1067 self._xmlFlushIfNecessary();
1068 finally:
1069 g_oLock.release();
1070 return None;
1071
1072 def doPollWork(self, sDebug = None):
1073 if self._asXml:
1074 g_oLock.acquire();
1075 try:
1076 self._xmlFlushIfNecessary(fPolling = True, sDebug = sDebug);
1077 finally:
1078 g_oLock.release();
1079 return None;
1080
1081
1082#
1083# Helpers
1084#
1085
1086g_fnComXcptFormatter = None;
1087
1088def setComXcptFormatter(fnCallback):
1089 """
1090 Install callback for prettier COM exception formatting.
1091
1092 The callback replaces the work done by format_exception_only() and
1093 takes the same arguments. It returns None if not interested in the
1094 exception.
1095 """
1096 global g_fnComXcptFormatter;
1097 g_fnComXcptFormatter = fnCallback;
1098 return True;
1099
1100def formatExceptionOnly(oType, oXcpt, sCaller, sTsPrf):
1101 """
1102 Wrapper around traceback.format_exception_only and __g_fnComXcptFormatter.
1103 """
1104 #asRet = ['oType=%s type(oXcpt)=%s' % (oType, type(oXcpt),)];
1105 asRet = [];
1106
1107 # Try the callback first.
1108 fnCallback = g_fnComXcptFormatter;
1109 if fnCallback:
1110 try:
1111 asRetCb = fnCallback(oType, oXcpt);
1112 if asRetCb:
1113 return asRetCb;
1114 #asRet += asRetCb;
1115 except:
1116 g_oReporter.log(0, '** internal-error: Hit exception #2 in __g_fnComXcptFormatter! %s'
1117 % (traceback.format_exc()), sCaller, sTsPrf);
1118 asRet += ['internal error: exception in __g_fnComXcptFormatter'];
1119
1120 # Now try format_exception_only:
1121 try:
1122 asRet += traceback.format_exception_only(oType, oXcpt);
1123 except:
1124 g_oReporter.log(0, '** internal-error: Hit exception #2 in format_exception_only! %s'
1125 % (traceback.format_exc()), sCaller, sTsPrf);
1126 asRet += ['internal error: Exception in format_exception_only!'];
1127 return asRet;
1128
1129
1130def logXcptWorker(iLevel, fIncErrors, sPrefix="", sText=None, cFrames=1):
1131 """
1132 Log an exception, optionally with a preceeding message and more than one
1133 call frame.
1134 """
1135 g_oLock.acquire();
1136 try:
1137
1138 if fIncErrors:
1139 g_oReporter.testIncErrors();
1140
1141 ## @todo skip all this if iLevel is too high!
1142
1143 # Try get exception info.
1144 sTsPrf = utils.getTimePrefix();
1145 try:
1146 oType, oValue, oTraceback = sys.exc_info();
1147 except:
1148 oType = oValue = oTraceback = None;
1149 if oType is not None:
1150
1151 # Try format the info
1152 try:
1153 rc = 0;
1154 sCaller = utils.getCallerName(oTraceback.tb_frame);
1155 if sText is not None:
1156 rc = g_oReporter.log(iLevel, "%s%s" % (sPrefix, sText), sCaller, sTsPrf);
1157 asInfo = None;
1158 try:
1159 asInfo = formatExceptionOnly(oType, oValue, sCaller, sTsPrf);
1160 atEntries = traceback.extract_tb(oTraceback);
1161 atEntries.reverse();
1162 if cFrames is not None and cFrames <= 1:
1163 if atEntries:
1164 asInfo = asInfo + traceback.format_list(atEntries[:1]);
1165 else:
1166 asInfo.append('Traceback (stack order):')
1167 if cFrames is not None and cFrames < len(atEntries):
1168 asInfo = asInfo + traceback.format_list(atEntries[:cFrames]);
1169 else:
1170 asInfo = asInfo + traceback.format_list(atEntries);
1171 asInfo.append('Stack:')
1172 asInfo = asInfo + traceback.format_stack(oTraceback.tb_frame.f_back, cFrames);
1173 except:
1174 g_oReporter.log(0, '** internal-error: Hit exception #2! %s' % (traceback.format_exc()), sCaller, sTsPrf);
1175
1176 if asInfo:
1177 # Do the logging.
1178 for sItem in asInfo:
1179 asLines = sItem.splitlines();
1180 for sLine in asLines:
1181 rc = g_oReporter.log(iLevel, '%s%s' % (sPrefix, sLine), sCaller, sTsPrf);
1182
1183 else:
1184 g_oReporter.log(iLevel, 'No exception info...', sCaller, sTsPrf);
1185 rc = -3;
1186 except:
1187 g_oReporter.log(0, '** internal-error: Hit exception! %s' % (traceback.format_exc()), None, sTsPrf);
1188 rc = -2;
1189 else:
1190 g_oReporter.log(0, '** internal-error: No exception! %s'
1191 % (utils.getCallerName(iFrame=3)), utils.getCallerName(iFrame=3), sTsPrf);
1192 rc = -1;
1193
1194 finally:
1195 g_oLock.release();
1196 return rc;
1197
1198
1199#
1200# The public Classes
1201#
1202class FileWrapper(object):
1203 """ File like class for TXS EXEC and similar. """
1204 def __init__(self, sPrefix):
1205 self.sPrefix = sPrefix;
1206
1207 def __del__(self):
1208 self.close();
1209
1210 def close(self):
1211 """ file.close """
1212 # Nothing to be done.
1213 return;
1214
1215 def read(self, cb):
1216 """file.read"""
1217 _ = cb;
1218 return "";
1219
1220 def write(self, sText):
1221 """file.write"""
1222 if not utils.isString(sText):
1223 if isinstance(sText, array.array):
1224 try:
1225 if sys.version_info < (3, 9, 0):
1226 # Removed since Python 3.9.
1227 sText = sText.tostring(); # pylint: disable=no-member
1228 else:
1229 sText = sText.tobytes();
1230 except:
1231 pass;
1232 if hasattr(sText, 'decode'):
1233 try:
1234 sText = sText.decode('utf-8', 'ignore');
1235 except:
1236 pass;
1237 g_oLock.acquire();
1238 try:
1239 sTsPrf = utils.getTimePrefix();
1240 sCaller = utils.getCallerName();
1241 asLines = sText.splitlines();
1242 for sLine in asLines:
1243 g_oReporter.log(0, '%s: %s' % (self.sPrefix, sLine), sCaller, sTsPrf);
1244 except:
1245 traceback.print_exc();
1246 finally:
1247 g_oLock.release();
1248 return None;
1249
1250class FileWrapperTestPipe(object):
1251 """
1252 File like class for the test pipe (TXS EXEC and similar).
1253
1254 This is also used to submit XML test result files.
1255 """
1256 def __init__(self):
1257 self.sPrefix = '';
1258 self.fStarted = False;
1259 self.fClosed = False;
1260 self.sTagBuffer = None;
1261 self.cTestDepth = 0;
1262 self.acTestErrors = [];
1263
1264 def __del__(self):
1265 self.close();
1266
1267 def close(self):
1268 """ file.close """
1269 if self.fStarted is True and self.fClosed is False:
1270 self.fClosed = True;
1271
1272 # Close open <Test> elements:
1273 if self.cTestDepth > 0:
1274 sNow = utils.getIsoTimestamp()
1275 cErrors = 0;
1276 while self.cTestDepth > 0:
1277 self.cTestDepth -= 1;
1278 if self.acTestErrors:
1279 cErrors += self.acTestErrors.pop();
1280 cErrors += 1;
1281 g_oReporter.subXmlWrite(self,
1282 '\n%s <Failed timestamp="%s" errors="%s"/>\n%s</Test>\n'
1283 % (' ' * self.cTestDepth, sNow, cErrors, ' ' * self.cTestDepth),
1284 utils.getCallerName());
1285
1286 # Tell the reporter that the XML input is done.
1287 try: g_oReporter.subXmlEnd(self);
1288 except:
1289 try: traceback.print_exc();
1290 except: pass;
1291 return True;
1292
1293 def read(self, cb = None):
1294 """file.read"""
1295 _ = cb;
1296 return "";
1297
1298 def write(self, sText):
1299 """file.write"""
1300 # lazy start.
1301 if self.fStarted is not True:
1302 try:
1303 g_oReporter.subXmlStart(self);
1304 except:
1305 traceback.print_exc();
1306 self.fStarted = True;
1307
1308 # Turn non-string stuff into strings.
1309 if not utils.isString(sText):
1310 if isinstance(sText, array.array):
1311 try:
1312 if sys.version_info < (3, 9, 0):
1313 # Removed since Python 3.9.
1314 sText = sText.tostring(); # pylint: disable=no-member
1315 else:
1316 sText = sText.tobytes();
1317 except:
1318 pass;
1319 if hasattr(sText, 'decode'):
1320 try: sText = sText.decode('utf-8', 'ignore');
1321 except: pass;
1322
1323 try:
1324 #
1325 # Write the XML to the reporter.
1326 #
1327 g_oReporter.subXmlWrite(self, sText, utils.getCallerName());
1328
1329 #
1330 # Parse the supplied text and look for <Failed.../> tags to keep track of the
1331 # error counter. This is only a very lazy aproach.
1332 #
1333 idxText = 0;
1334 while sText:
1335 if self.sTagBuffer is None:
1336 # Look for the start of a tag.
1337 idxStart = sText.find('<', idxText);
1338 if idxStart != -1:
1339 # If the end was found inside the current buffer, parse the line,
1340 # otherwise we have to save it for later.
1341 idxEnd = sText.find('>', idxStart);
1342 if idxEnd != -1:
1343 self._processXmlElement(sText[idxStart:idxEnd+1]);
1344 idxText = idxEnd;
1345 else:
1346 self.sTagBuffer = sText[idxStart:];
1347 break;
1348 else:
1349 break;
1350 else:
1351 # Search for the end of the tag and parse the whole tag.
1352 assert(idxText == 0);
1353 idxEnd = sText.find('>');
1354 if idxEnd != -1:
1355 self._processXmlElement(self.sTagBuffer + sText[:idxEnd+1]);
1356 self.sTagBuffer = None;
1357 idxText = idxEnd;
1358 else:
1359 self.sTagBuffer = self.sTagBuffer + sText[idxText:];
1360 break;
1361 except:
1362 traceback.print_exc();
1363 return None;
1364
1365 def _processXmlElement(self, sElement):
1366 """
1367 Processes a complete XML tag.
1368
1369 We handle the 'Failed' tag to keep track of the error counter.
1370 We also track 'Test' tags to make sure we close with all of them properly closed.
1371 """
1372 # Make sure we don't parse any space between < and the element name.
1373 sElement = sElement.strip();
1374
1375 # Find the end of the name
1376 idxEndName = sElement.find(' ');
1377 if idxEndName == -1:
1378 idxEndName = sElement.find('>');
1379 if idxEndName >= 0:
1380 if sElement[idxEndName - 1] == '/':
1381 idxEndName -= 1;
1382 else:
1383 idxEndName = len(sElement);
1384 sElementName = sElement[1:idxEndName];
1385
1386 # <Failed>:
1387 if sElementName == 'Failed':
1388 g_oLock.acquire();
1389 try:
1390 g_oReporter.testIncErrors();
1391 finally:
1392 g_oLock.release();
1393 if self.acTestErrors:
1394 self.acTestErrors[-1] += 1; # get errors attrib
1395 # <Test>
1396 elif sElementName == 'Test':
1397 self.cTestDepth += 1;
1398 self.acTestErrors.append(0);
1399 # </Test>
1400 elif sElementName == '/Test':
1401 self.cTestDepth -= 1;
1402 if self.acTestErrors:
1403 cErrors = self.acTestErrors.pop();
1404 if self.acTestErrors:
1405 self.acTestErrors[-1] += cErrors;
1406
1407
1408#
1409# The public APIs.
1410#
1411
1412def log(sText, sCaller = None):
1413 """Writes the specfied text to the log."""
1414 g_oLock.acquire();
1415 try:
1416 rc = g_oReporter.log(1, sText, sCaller if sCaller else utils.getCallerName(), utils.getTimePrefix());
1417 except:
1418 rc = -1;
1419 finally:
1420 g_oLock.release();
1421 return rc;
1422
1423def logXcpt(sText=None, cFrames=1):
1424 """
1425 Log an exception, optionally with a preceeding message and more than one
1426 call frame.
1427 """
1428 return logXcptWorker(1, False, "", sText, cFrames);
1429
1430def log2(sText, sCaller = None):
1431 """Log level 2: Writes the specfied text to the log."""
1432 g_oLock.acquire();
1433 try:
1434 rc = g_oReporter.log(2, sText, sCaller if sCaller else utils.getCallerName(), utils.getTimePrefix());
1435 except:
1436 rc = -1;
1437 finally:
1438 g_oLock.release();
1439 return rc;
1440
1441def log2Xcpt(sText=None, cFrames=1):
1442 """
1443 Log level 2: Log an exception, optionally with a preceeding message and
1444 more than one call frame.
1445 """
1446 return logXcptWorker(2, False, "", sText, cFrames);
1447
1448def log3(sText, sCaller = None):
1449 """Log level 3: Writes the specfied text to the log."""
1450 g_oLock.acquire();
1451 try:
1452 rc = g_oReporter.log(3, sText, sCaller if sCaller else utils.getCallerName(), utils.getTimePrefix());
1453 except:
1454 rc = -1;
1455 finally:
1456 g_oLock.release();
1457 return rc;
1458
1459def log3Xcpt(sText=None, cFrames=1):
1460 """
1461 Log level 3: Log an exception, optionally with a preceeding message and
1462 more than one call frame.
1463 """
1464 return logXcptWorker(3, False, "", sText, cFrames);
1465
1466def log4(sText, sCaller = None):
1467 """Log level 4: Writes the specfied text to the log."""
1468 g_oLock.acquire();
1469 try:
1470 rc = g_oReporter.log(4, sText, sCaller if sCaller else utils.getCallerName(), utils.getTimePrefix());
1471 except:
1472 rc = -1;
1473 finally:
1474 g_oLock.release();
1475 return rc;
1476
1477def log4Xcpt(sText=None, cFrames=1):
1478 """
1479 Log level 4: Log an exception, optionally with a preceeding message and
1480 more than one call frame.
1481 """
1482 return logXcptWorker(4, False, "", sText, cFrames);
1483
1484def log5(sText, sCaller = None):
1485 """Log level 2: Writes the specfied text to the log."""
1486 g_oLock.acquire();
1487 try:
1488 rc = g_oReporter.log(5, sText, sCaller if sCaller else utils.getCallerName(), utils.getTimePrefix());
1489 except:
1490 rc = -1;
1491 finally:
1492 g_oLock.release();
1493 return rc;
1494
1495def log5Xcpt(sText=None, cFrames=1):
1496 """
1497 Log level 5: Log an exception, optionally with a preceeding message and
1498 more than one call frame.
1499 """
1500 return logXcptWorker(5, False, "", sText, cFrames);
1501
1502def log6(sText, sCaller = None):
1503 """Log level 6: Writes the specfied text to the log."""
1504 g_oLock.acquire();
1505 try:
1506 rc = g_oReporter.log(6, sText, sCaller if sCaller else utils.getCallerName(), utils.getTimePrefix());
1507 except:
1508 rc = -1;
1509 finally:
1510 g_oLock.release();
1511 return rc;
1512
1513def log6Xcpt(sText=None, cFrames=1):
1514 """
1515 Log level 6: Log an exception, optionally with a preceeding message and
1516 more than one call frame.
1517 """
1518 return logXcptWorker(6, False, "", sText, cFrames);
1519
1520def maybeErr(fIsError, sText):
1521 """ Maybe error or maybe normal log entry. """
1522 if fIsError is True:
1523 return error(sText, sCaller = utils.getCallerName());
1524 return log(sText, sCaller = utils.getCallerName());
1525
1526def maybeErrXcpt(fIsError, sText=None, cFrames=1):
1527 """ Maybe error or maybe normal log exception entry. """
1528 if fIsError is True:
1529 return errorXcpt(sText, cFrames);
1530 return logXcpt(sText, cFrames);
1531
1532def maybeLog(fIsNotError, sText):
1533 """ Maybe error or maybe normal log entry. """
1534 if fIsNotError is not True:
1535 return error(sText, sCaller = utils.getCallerName());
1536 return log(sText, sCaller = utils.getCallerName());
1537
1538def maybeLogXcpt(fIsNotError, sText=None, cFrames=1):
1539 """ Maybe error or maybe normal log exception entry. """
1540 if fIsNotError is not True:
1541 return errorXcpt(sText, cFrames);
1542 return logXcpt(sText, cFrames);
1543
1544def error(sText, sCaller = None):
1545 """
1546 Writes the specfied error message to the log.
1547
1548 This will add an error to the current test.
1549
1550 Always returns False for the convenience of methods returning boolean
1551 success indicators.
1552 """
1553 g_oLock.acquire();
1554 try:
1555 g_oReporter.testIncErrors();
1556 g_oReporter.log(0, '** error: %s' % (sText), sCaller if sCaller else utils.getCallerName(), utils.getTimePrefix());
1557 except:
1558 pass;
1559 finally:
1560 g_oLock.release();
1561 return False;
1562
1563def errorXcpt(sText=None, cFrames=1):
1564 """
1565 Log an error caused by an exception. If sText is given, it will preceed
1566 the exception information. cFrames can be used to display more stack.
1567
1568 This will add an error to the current test.
1569
1570 Always returns False for the convenience of methods returning boolean
1571 success indicators.
1572 """
1573 logXcptWorker(0, True, '** error: ', sText, cFrames);
1574 return False;
1575
1576def errorTimeout(sText):
1577 """
1578 Flags the current test as having timed out and writes the specified message to the log.
1579
1580 This will add an error to the current test.
1581
1582 Always returns False for the convenience of methods returning boolean
1583 success indicators.
1584 """
1585 g_oLock.acquire();
1586 try:
1587 g_oReporter.testSetTimedOut();
1588 g_oReporter.log(0, '** timeout-error: %s' % (sText), utils.getCallerName(), utils.getTimePrefix());
1589 except:
1590 pass;
1591 finally:
1592 g_oLock.release();
1593 return False;
1594
1595def fatal(sText):
1596 """
1597 Writes a fatal error to the log.
1598
1599 This will add an error to the current test.
1600
1601 Always returns False for the convenience of methods returning boolean
1602 success indicators.
1603 """
1604 g_oLock.acquire();
1605 try:
1606 g_oReporter.testIncErrors();
1607 g_oReporter.log(0, '** fatal error: %s' % (sText), utils.getCallerName(), utils.getTimePrefix());
1608 except:
1609 pass
1610 finally:
1611 g_oLock.release();
1612 return False;
1613
1614def fatalXcpt(sText=None, cFrames=1):
1615 """
1616 Log a fatal error caused by an exception. If sText is given, it will
1617 preceed the exception information. cFrames can be used to display more
1618 stack.
1619
1620 This will add an error to the current test.
1621
1622 Always returns False for the convenience of methods returning boolean
1623 success indicators.
1624 """
1625 logXcptWorker(0, True, "** fatal error: ", sText, cFrames);
1626 return False;
1627
1628def addLogFile(sFilename, sKind, sDescription = '', sAltName = None):
1629 """
1630 Adds the specified log file to the report if the file exists.
1631
1632 The sDescription is a free form description of the log file.
1633
1634 The sKind parameter is for adding some machine parsable hint what kind of
1635 log file this really is.
1636
1637 Returns True on success, False on failure (no ENOENT errors are logged).
1638 """
1639 sTsPrf = utils.getTimePrefix();
1640 sCaller = utils.getCallerName();
1641 fRc = False;
1642 if sAltName is None:
1643 sAltName = sFilename;
1644
1645 try:
1646 oSrcFile = utils.openNoInherit(sFilename, 'rb');
1647 except IOError as oXcpt:
1648 if oXcpt.errno != errno.ENOENT:
1649 logXcpt('addLogFile(%s,%s,%s)' % (sFilename, sDescription, sKind));
1650 else:
1651 logXcpt('addLogFile(%s,%s,%s) IOError' % (sFilename, sDescription, sKind));
1652 except:
1653 logXcpt('addLogFile(%s,%s,%s)' % (sFilename, sDescription, sKind));
1654 else:
1655 g_oLock.acquire();
1656 try:
1657 fRc = g_oReporter.addLogFile(oSrcFile, sFilename, sAltName, sDescription, sKind, sCaller, sTsPrf);
1658 finally:
1659 g_oLock.release();
1660 oSrcFile.close();
1661 return fRc;
1662
1663def addLogString(sLog, sLogName, sKind, sDescription = ''):
1664 """
1665 Adds the specified log string to the report.
1666
1667 The sLog parameter sets the name of the log file.
1668
1669 The sDescription is a free form description of the log file.
1670
1671 The sKind parameter is for adding some machine parsable hint what kind of
1672 log file this really is.
1673
1674 Returns True on success, False on failure (no ENOENT errors are logged).
1675 """
1676 sTsPrf = utils.getTimePrefix();
1677 sCaller = utils.getCallerName();
1678 fRc = False;
1679
1680 g_oLock.acquire();
1681 try:
1682 fRc = g_oReporter.addLogString(sLog, sLogName, sDescription, sKind, sCaller, sTsPrf);
1683 finally:
1684 g_oLock.release();
1685 return fRc;
1686
1687def isLocal():
1688 """Is this a local reporter?"""
1689 return g_oReporter.isLocal()
1690
1691def incVerbosity():
1692 """Increases the verbosity level."""
1693 return g_oReporter.incVerbosity()
1694
1695def incDebug():
1696 """Increases the debug level."""
1697 return g_oReporter.incDebug()
1698
1699def getVerbosity():
1700 """Returns the current verbosity level."""
1701 return g_oReporter.getVerbosity()
1702
1703def getDebug():
1704 """Returns the current debug level."""
1705 return g_oReporter.getDebug()
1706
1707def appendToProcessName(sAppend):
1708 """
1709 Appends sAppend to the base process name.
1710 Returns the new process name.
1711 """
1712 return g_oReporter.appendToProcessName(sAppend);
1713
1714def getErrorCount():
1715 """
1716 Get the current error count for the entire test run.
1717 """
1718 g_oLock.acquire();
1719 try:
1720 cErrors = g_oReporter.cErrors;
1721 finally:
1722 g_oLock.release();
1723 return cErrors;
1724
1725def doPollWork(sDebug = None):
1726 """
1727 This can be called from wait loops and similar to make the reporter call
1728 home with pending XML and such.
1729 """
1730 g_oReporter.doPollWork(sDebug);
1731 return None;
1732
1733
1734#
1735# Test reporting, a bit similar to RTTestI*.
1736#
1737
1738def testStart(sName):
1739 """
1740 Starts a new test (pushes it).
1741 """
1742 g_oLock.acquire();
1743 try:
1744 rc = g_oReporter.testStart(sName, utils.getCallerName());
1745 finally:
1746 g_oLock.release();
1747 return rc;
1748
1749def testValue(sName, sValue, sUnit):
1750 """
1751 Reports a benchmark value or something simiarlly useful.
1752 """
1753 g_oLock.acquire();
1754 try:
1755 rc = g_oReporter.testValue(sName, str(sValue), sUnit, utils.getCallerName());
1756 finally:
1757 g_oLock.release();
1758 return rc;
1759
1760def testFailure(sDetails):
1761 """
1762 Reports a failure.
1763 We count these calls and testDone will use them to report PASSED or FAILED.
1764
1765 Returns False so that a return False line can be saved.
1766 """
1767 g_oLock.acquire();
1768 try:
1769 g_oReporter.testFailure(sDetails, utils.getCallerName());
1770 finally:
1771 g_oLock.release();
1772 return False;
1773
1774def testFailureXcpt(sDetails = ''):
1775 """
1776 Reports a failure with exception.
1777 We count these calls and testDone will use them to report PASSED or FAILED.
1778
1779 Returns False so that a return False line can be saved.
1780 """
1781 # Extract exception info.
1782 try:
1783 oType, oValue, oTraceback = sys.exc_info();
1784 except:
1785 oType = oValue, oTraceback = None;
1786 if oType is not None:
1787 sCaller = utils.getCallerName(oTraceback.tb_frame);
1788 sXcpt = ' '.join(formatExceptionOnly(oType, oValue, sCaller, utils.getTimePrefix()));
1789 else:
1790 sCaller = utils.getCallerName();
1791 sXcpt = 'No exception at %s' % (sCaller,);
1792
1793 # Use testFailure to do the work.
1794 g_oLock.acquire();
1795 try:
1796 if sDetails == '':
1797 g_oReporter.testFailure('Exception: %s' % (sXcpt,), sCaller);
1798 else:
1799 g_oReporter.testFailure('%s: %s' % (sDetails, sXcpt), sCaller);
1800 finally:
1801 g_oLock.release();
1802 return False;
1803
1804def testDone(fSkipped = False):
1805 """
1806 Completes the current test (pops it), logging PASSED / FAILURE.
1807
1808 Returns a tuple with the name of the test and its error count.
1809 """
1810 g_oLock.acquire();
1811 try:
1812 rc = g_oReporter.testDone(fSkipped, utils.getCallerName());
1813 finally:
1814 g_oLock.release();
1815 return rc;
1816
1817def testErrorCount():
1818 """
1819 Gets the error count of the current test.
1820
1821 Returns the number of errors.
1822 """
1823 g_oLock.acquire();
1824 try:
1825 cErrors = g_oReporter.testErrorCount();
1826 finally:
1827 g_oLock.release();
1828 return cErrors;
1829
1830def testCleanup():
1831 """
1832 Closes all open tests with a generic error condition.
1833
1834 Returns True if no open tests, False if something had to be closed with failure.
1835 """
1836 g_oLock.acquire();
1837 try:
1838 fRc = g_oReporter.testCleanup(utils.getCallerName());
1839 g_oReporter.xmlFlush(fRetry = False, fForce = True);
1840 finally:
1841 g_oLock.release();
1842 fRc = False;
1843 return fRc;
1844
1845
1846#
1847# Sub XML stuff.
1848#
1849
1850def addSubXmlFile(sFilename):
1851 """
1852 Adds a sub-xml result file to the party.
1853 """
1854 fRc = False;
1855 try:
1856 oSrcFile = utils.openNoInherit(sFilename, 'r');
1857 except IOError as oXcpt:
1858 if oXcpt.errno != errno.ENOENT:
1859 logXcpt('addSubXmlFile(%s)' % (sFilename,));
1860 except:
1861 logXcpt('addSubXmlFile(%s)' % (sFilename,));
1862 else:
1863 try:
1864 oWrapper = FileWrapperTestPipe()
1865 oWrapper.write(oSrcFile.read());
1866 oWrapper.close();
1867 except:
1868 logXcpt('addSubXmlFile(%s)' % (sFilename,));
1869 oSrcFile.close();
1870
1871 return fRc;
1872
1873
1874#
1875# Other useful debugging tools.
1876#
1877
1878def logAllStacks(cFrames = None):
1879 """
1880 Logs the stacks of all python threads.
1881 """
1882 sTsPrf = utils.getTimePrefix();
1883 sCaller = utils.getCallerName();
1884 g_oLock.acquire();
1885
1886 cThread = 0;
1887 for idThread, oStack in sys._current_frames().items(): # >=2.5, a bit ugly - pylint: disable=protected-access
1888 try:
1889 if cThread > 0:
1890 g_oReporter.log(1, '', sCaller, sTsPrf);
1891 g_oReporter.log(1, 'Thread %s (%#x)' % (idThread, idThread), sCaller, sTsPrf);
1892 try:
1893 asInfo = traceback.format_stack(oStack, cFrames);
1894 except:
1895 g_oReporter.log(1, ' Stack formatting failed w/ exception', sCaller, sTsPrf);
1896 else:
1897 for sInfo in asInfo:
1898 asLines = sInfo.splitlines();
1899 for sLine in asLines:
1900 g_oReporter.log(1, sLine, sCaller, sTsPrf);
1901 except:
1902 pass;
1903 cThread += 1;
1904
1905 g_oLock.release();
1906 return None;
1907
1908def checkTestManagerConnection():
1909 """
1910 Checks the connection to the test manager.
1911
1912 Returns True if the connection is fine, False if not, None if not remote
1913 reporter.
1914
1915 Note! This as the sideeffect of flushing XML.
1916 """
1917 g_oLock.acquire();
1918 try:
1919 fRc = g_oReporter.xmlFlush(fRetry = False, fForce = True);
1920 finally:
1921 g_oLock.release();
1922 fRc = False;
1923 return fRc;
1924
1925def flushall(fSkipXml = False):
1926 """
1927 Flushes all output streams, both standard and logger related.
1928 This may also push data to the remote test manager.
1929 """
1930 try: sys.stdout.flush();
1931 except: pass;
1932 try: sys.stderr.flush();
1933 except: pass;
1934
1935 if fSkipXml is not True:
1936 g_oLock.acquire();
1937 try:
1938 g_oReporter.xmlFlush(fRetry = False);
1939 finally:
1940 g_oLock.release();
1941
1942 return True;
1943
1944
1945#
1946# Module initialization.
1947#
1948
1949def _InitReporterModule():
1950 """
1951 Instantiate the test reporter.
1952 """
1953 global g_oReporter, g_sReporterName
1954
1955 g_sReporterName = os.getenv("TESTBOX_REPORTER", "local");
1956 if g_sReporterName == "local":
1957 g_oReporter = LocalReporter();
1958 elif g_sReporterName == "remote":
1959 g_oReporter = RemoteReporter(); # Correct, but still plain stupid. pylint: disable=redefined-variable-type
1960 else:
1961 print(os.path.basename(__file__) + ": Unknown TESTBOX_REPORTER value: '" + g_sReporterName + "'", file = sys.stderr);
1962 raise Exception("Unknown TESTBOX_REPORTER value '" + g_sReporterName + "'");
1963
1964if __name__ != "checker": # pychecker avoidance.
1965 _InitReporterModule();
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette