VirtualBox

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

Last change on this file since 82525 was 79915, checked in by vboxsync, 5 years ago

ValKit/reporter.py: More log levels.

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