VirtualBox

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

Last change on this file since 96407 was 96407, checked in by vboxsync, 2 years ago

scm copyright and license note update

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