VirtualBox

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

Last change on this file since 70573 was 70573, checked in by vboxsync, 7 years ago

testdriver/reporter.py: Python 3 adjustments.

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

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