VirtualBox

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

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

testboxdriver,testboxscript: Python3 updates.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 59.6 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: reporter.py 70566 2018-01-12 18:25:48Z 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: 70566 $"
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 print(sText.encode('ascii', 'replace'), file = self.oOutput);
715 if self.fFlushEachLine: self.oOutput.flush();
716 return None;
717
718 #
719 # Talking to TM.
720 #
721
722 def _processTmStatusResponse(self, oConn, sOperation, fClose = True):
723 """
724 Processes HTTP reponse from the test manager.
725 Returns True, False or None. None should be retried, the others not.
726 May raise exception on HTTP issue (retry ok).
727 """
728 import httplib;
729 from common import constants;
730
731 # Read the response and (optionally) close the connection.
732 oResponse = oConn.getresponse();
733 try:
734 sRspBody = oResponse.read();
735 except httplib.IncompleteRead as oXcpt:
736 self._writeOutput('%s: %s: Warning: httplib.IncompleteRead: %s [expected %s, got %s]'
737 % (utils.getTimePrefix(), sOperation, oXcpt, oXcpt.expected, len(oXcpt.partial),));
738 sRspBody = oXcpt.partial;
739 if fClose is True:
740 try: oConn.close();
741 except: pass;
742
743 # Check the content type.
744 sContentType = oResponse.getheader('Content-Type');
745 if sContentType is not None and sContentType == 'application/x-www-form-urlencoded; charset=utf-8':
746
747 # Parse the body and check the RESULT parameter.
748 dResponse = self._fnUrlParseQs(sRspBody, strict_parsing = True);
749 sResult = dResponse.get(constants.tbresp.ALL_PARAM_RESULT, None);
750 if isinstance(sResult, list):
751 sResult = sResult[0] if len(sResult) == 1 else '%d results' % (len(sResult),);
752
753 if sResult is not None:
754 if sResult == constants.tbresp.STATUS_ACK:
755 return True;
756 if sResult == constants.tbresp.STATUS_NACK:
757 self._writeOutput('%s: %s: Failed (%s). (dResponse=%s)'
758 % (utils.getTimePrefix(), sOperation, sResult, dResponse,));
759 return False;
760
761 self._writeOutput('%s: %s: Failed - dResponse=%s' % (utils.getTimePrefix(), sOperation, dResponse,));
762 else:
763 self._writeOutput('%s: %s: Unexpected Content-Type: %s' % (utils.getTimePrefix(), sOperation, sContentType,));
764 self._writeOutput('%s: %s: Body: %s' % (utils.getTimePrefix(), sOperation, sRspBody,));
765 return None;
766
767 def _doUploadFile(self, oSrcFile, sSrcFilename, sDescription, sKind, sMime):
768 """ Uploads the given file to the test manager. """
769
770 # Prepare header and url.
771 dHeader = dict(self._dHttpHeader);
772 dHeader['Content-Type'] = 'application/octet-stream';
773 self._writeOutput('%s: _doUploadFile: sHeader=%s' % (utils.getTimePrefix(), dHeader,));
774 oSrcFile.seek(0, 2);
775 self._writeOutput('%s: _doUploadFile: size=%d' % (utils.getTimePrefix(), oSrcFile.tell(),));
776 oSrcFile.seek(0);
777
778 from common import constants;
779 sUrl = self._sTmServerPath + '&' \
780 + self._fnUrlEncode({ constants.tbreq.UPLOAD_PARAM_NAME: os.path.basename(sSrcFilename),
781 constants.tbreq.UPLOAD_PARAM_DESC: sDescription,
782 constants.tbreq.UPLOAD_PARAM_KIND: sKind,
783 constants.tbreq.UPLOAD_PARAM_MIME: sMime,
784 constants.tbreq.ALL_PARAM_ACTION: constants.tbreq.UPLOAD,
785 });
786
787 # Retry loop.
788 secStart = utils.timestampSecond();
789 while True:
790 try:
791 oConn = self._fnTmConnect();
792 oConn.request('POST', sUrl, oSrcFile.read(), dHeader);
793 fRc = self._processTmStatusResponse(oConn, '_doUploadFile', fClose = True);
794 oConn.close();
795 if fRc is not None:
796 return fRc;
797 except:
798 logXcpt('warning: exception during UPLOAD request');
799
800 if utils.timestampSecond() - secStart >= self.kcSecTestManagerRetryTimeout:
801 self._writeOutput('%s: _doUploadFile: Timed out.' % (utils.getTimePrefix(),));
802 break;
803 try: oSrcFile.seek(0);
804 except:
805 logXcpt();
806 break;
807 self._writeOutput('%s: _doUploadFile: Retrying...' % (utils.getTimePrefix(), ));
808 time.sleep(2);
809
810 return False;
811
812 def _doUploadString(self, sSrc, sSrcName, sDescription, sKind, sMime):
813 """ Uploads the given string as a separate file to the test manager. """
814
815 # Prepare header and url.
816 dHeader = dict(self._dHttpHeader);
817 dHeader['Content-Type'] = 'application/octet-stream';
818 self._writeOutput('%s: _doUploadString: sHeader=%s' % (utils.getTimePrefix(), dHeader,));
819 self._writeOutput('%s: _doUploadString: size=%d' % (utils.getTimePrefix(), sys.getsizeof(sSrc),));
820
821 from common import constants;
822 sUrl = self._sTmServerPath + '&' \
823 + self._fnUrlEncode({ constants.tbreq.UPLOAD_PARAM_NAME: os.path.basename(sSrcName),
824 constants.tbreq.UPLOAD_PARAM_DESC: sDescription,
825 constants.tbreq.UPLOAD_PARAM_KIND: sKind,
826 constants.tbreq.UPLOAD_PARAM_MIME: sMime,
827 constants.tbreq.ALL_PARAM_ACTION: constants.tbreq.UPLOAD,
828 });
829
830 # Retry loop.
831 secStart = utils.timestampSecond();
832 while True:
833 try:
834 oConn = self._fnTmConnect();
835 oConn.request('POST', sUrl, sSrc, dHeader);
836 fRc = self._processTmStatusResponse(oConn, '_doUploadString', fClose = True);
837 oConn.close();
838 if fRc is not None:
839 return fRc;
840 except:
841 logXcpt('warning: exception during UPLOAD request');
842
843 if utils.timestampSecond() - secStart >= self.kcSecTestManagerRetryTimeout:
844 self._writeOutput('%s: _doUploadString: Timed out.' % (utils.getTimePrefix(),));
845 break;
846 self._writeOutput('%s: _doUploadString: Retrying...' % (utils.getTimePrefix(), ));
847 time.sleep(2);
848
849 return False;
850
851 def _xmlDoFlush(self, asXml, fRetry = False, fDtor = False):
852 """
853 The code that does the actual talking to the server.
854 Used by both xmlFlush and __del__.
855 """
856 secStart = utils.timestampSecond();
857 while True:
858 fRc = None;
859 try:
860 # Post.
861 from common import constants;
862 sPostBody = self._fnUrlEncode({constants.tbreq.XML_RESULT_PARAM_BODY: '\n'.join(asXml),});
863 oConn = self._fnTmConnect();
864 oConn.request('POST',
865 self._sTmServerPath + ('&%s=%s' % (constants.tbreq.ALL_PARAM_ACTION, constants.tbreq.XML_RESULTS)),
866 sPostBody,
867 self._dHttpHeader);
868
869 fRc = self._processTmStatusResponse(oConn, '_xmlDoFlush', fClose = True);
870 if fRc is True:
871 if self.fDebugXml:
872 self._writeOutput('_xmlDoFlush:\n%s' % ('\n'.join(asXml),));
873 return (None, False);
874 if fRc is False:
875 self._writeOutput('_xmlDoFlush: Failed - we should abort the test, really.');
876 return (None, True);
877 except Exception as oXcpt:
878 if not fDtor:
879 logXcpt('warning: exception during XML_RESULTS request');
880 else:
881 self._writeOutput('warning: exception during XML_RESULTS request: %s' % (oXcpt,));
882
883 if fRetry is not True \
884 or utils.timestampSecond() - secStart >= self.kcSecTestManagerRetryTimeout:
885 break;
886 time.sleep(2);
887
888 return (asXml, False);
889
890
891 #
892 # Overridden methods.
893 #
894
895 def isLocal(self):
896 return False;
897
898 def log(self, iLevel, sText, sCaller, sTsPrf):
899 if iLevel <= self.iVerbose:
900 if self.iDebug > 0:
901 sLogText = '%s %30s: %s' % (sTsPrf, sCaller, sText);
902 else:
903 sLogText = '%s %s: %s' % (sTsPrf, self.sName, sText);
904 self._writeOutput(sLogText);
905 return 0;
906
907 def addLogFile(self, oSrcFile, sSrcFilename, sAltName, sDescription, sKind, sCaller, sTsPrf):
908 fRc = True;
909 if sKind in [ 'text', 'log', 'process'] \
910 or sKind.startswith('log/') \
911 or sKind.startswith('info/') \
912 or sKind.startswith('process/'):
913 self.log(0, '*** Uploading "%s" - KIND: "%s" - DESC: "%s" ***'
914 % (sSrcFilename, sKind, sDescription), sCaller, sTsPrf);
915 self.xmlFlush();
916 g_oLock.release();
917 try:
918 self._doUploadFile(oSrcFile, sAltName, sDescription, sKind, 'text/plain');
919 finally:
920 g_oLock.acquire();
921 elif sKind.startswith('screenshot/'):
922 self.log(0, '*** Uploading "%s" - KIND: "%s" - DESC: "%s" ***'
923 % (sSrcFilename, sKind, sDescription), sCaller, sTsPrf);
924 self.xmlFlush();
925 g_oLock.release();
926 try:
927 self._doUploadFile(oSrcFile, sAltName, sDescription, sKind, 'image/png');
928 finally:
929 g_oLock.acquire();
930 elif sKind.startswith('misc/'):
931 self.log(0, '*** Uploading "%s" - KIND: "%s" - DESC: "%s" ***'
932 % (sSrcFilename, sKind, sDescription), sCaller, sTsPrf);
933 self.xmlFlush();
934 g_oLock.release();
935 try:
936 self._doUploadFile(oSrcFile, sAltName, sDescription, sKind, 'application/octet-stream');
937 finally:
938 g_oLock.acquire();
939 else:
940 self.log(0, '*** UNKNOWN FILE "%s" - KIND "%s" - DESC "%s" ***'
941 % (sSrcFilename, sKind, sDescription), sCaller, sTsPrf);
942 return fRc;
943
944 def addLogString(self, sLog, sLogName, 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 % (sLogName, sKind, sDescription), sCaller, sTsPrf);
952 self.xmlFlush();
953 g_oLock.release();
954 try:
955 self._doUploadString(sLog, sLogName, sDescription, sKind, 'text/plain');
956 finally:
957 g_oLock.acquire();
958 else:
959 self.log(0, '*** UNKNOWN FILE "%s" - KIND "%s" - DESC "%s" ***'
960 % (sLogName, sKind, sDescription), sCaller, sTsPrf);
961 return fRc;
962
963 def xmlFlush(self, fRetry = False, fForce = False):
964 """
965 Flushes the XML back log. Called with the lock held, may leave it
966 while communicating with the server.
967 """
968 if not self._fXmlFlushing:
969 asXml = self._asXml;
970 self._asXml = [];
971 if asXml or fForce is True:
972 self._fXmlFlushing = True;
973
974 g_oLock.release();
975 try:
976 (asXml, fIncErrors) = self._xmlDoFlush(asXml, fRetry = fRetry);
977 finally:
978 g_oLock.acquire();
979
980 if fIncErrors:
981 self.testIncErrors();
982
983 self._fXmlFlushing = False;
984 if asXml is None:
985 self._secTsXmlFlush = utils.timestampSecond();
986 else:
987 self._asXml = asXml + self._asXml;
988 return True;
989
990 self._secTsXmlFlush = utils.timestampSecond();
991 return False;
992
993 def _xmlFlushIfNecessary(self, fPolling = False, sDebug = None):
994 """Flushes the XML back log if necessary."""
995 tsNow = utils.timestampSecond();
996 cSecs = tsNow - self._secTsXmlFlush;
997 cSecsLast = tsNow - self._secTsXmlLast;
998 if fPolling is not True:
999 self._secTsXmlLast = tsNow;
1000
1001 # Absolute flush thresholds.
1002 if cSecs >= self.kcSecXmlFlushMax:
1003 return self.xmlFlush();
1004 if len(self._asXml) >= self.kcLinesXmlFlush:
1005 return self.xmlFlush();
1006
1007 # Flush if idle long enough.
1008 if cSecs >= self.kcSecXmlFlushMin \
1009 and cSecsLast >= self.kcSecXmlFlushIdle:
1010 return self.xmlFlush();
1011
1012 _ = sDebug;
1013 return False;
1014
1015 def _xmlWrite(self, asText, fIndent = True):
1016 """XML output function for the reporter."""
1017 self._asXml += asText;
1018 self._xmlFlushIfNecessary();
1019 _ = fIndent; # No pretty printing, thank you.
1020 return None;
1021
1022 def subXmlStart(self, oFileWrapper):
1023 oFileWrapper.sXmlBuffer = '';
1024 return None;
1025
1026 def subXmlWrite(self, oFileWrapper, sRawXml, sCaller):
1027 oFileWrapper.sXmlBuffer += sRawXml;
1028 _ = sCaller;
1029 return None;
1030
1031 def subXmlEnd(self, oFileWrapper):
1032 sRawXml = oFileWrapper.sXmlBuffer;
1033 ## @todo should validate the document here and maybe auto terminate things. Adding some hints to have the server do
1034 # this instead.
1035 g_oLock.acquire();
1036 try:
1037 self._asXml += [ '<PushHint testdepth="%d"/>' % (len(self.atTests),),
1038 sRawXml,
1039 '<PopHint testdepth="%d"/>' % (len(self.atTests),),];
1040 self._xmlFlushIfNecessary();
1041 finally:
1042 g_oLock.release();
1043 return None;
1044
1045 def doPollWork(self, sDebug = None):
1046 if self._asXml:
1047 g_oLock.acquire();
1048 try:
1049 self._xmlFlushIfNecessary(fPolling = True, sDebug = sDebug);
1050 finally:
1051 g_oLock.release();
1052 return None;
1053
1054
1055#
1056# Helpers
1057#
1058
1059def logXcptWorker(iLevel, fIncErrors, sPrefix="", sText=None, cFrames=1):
1060 """
1061 Log an exception, optionally with a preceeding message and more than one
1062 call frame.
1063 """
1064 g_oLock.acquire();
1065 try:
1066
1067 if fIncErrors:
1068 g_oReporter.testIncErrors();
1069
1070 ## @todo skip all this if iLevel is too high!
1071
1072 # Try get exception info.
1073 sTsPrf = utils.getTimePrefix();
1074 try:
1075 oType, oValue, oTraceback = sys.exc_info();
1076 except:
1077 oType = oValue = oTraceback = None;
1078 if oType is not None:
1079
1080 # Try format the info
1081 try:
1082 rc = 0;
1083 sCaller = utils.getCallerName(oTraceback.tb_frame);
1084 if sText is not None:
1085 rc = g_oReporter.log(iLevel, "%s%s" % (sPrefix, sText), sCaller, sTsPrf);
1086 asInfo = [];
1087 try:
1088 asInfo = asInfo + traceback.format_exception_only(oType, oValue);
1089 if cFrames is not None and cFrames <= 1:
1090 asInfo = asInfo + traceback.format_tb(oTraceback, 1);
1091 else:
1092 asInfo.append('Traceback:')
1093 asInfo = asInfo + traceback.format_tb(oTraceback, cFrames);
1094 asInfo.append('Stack:')
1095 asInfo = asInfo + traceback.format_stack(oTraceback.tb_frame.f_back, cFrames);
1096 except:
1097 g_oReporter.log(0, '** internal-error: Hit exception #2! %s' % (traceback.format_exc()), sCaller, sTsPrf);
1098
1099 if asInfo:
1100 # Do the logging.
1101 for sItem in asInfo:
1102 asLines = sItem.splitlines();
1103 for sLine in asLines:
1104 rc = g_oReporter.log(iLevel, '%s%s' % (sPrefix, sLine), sCaller, sTsPrf);
1105
1106 else:
1107 g_oReporter.log(iLevel, 'No exception info...', sCaller, sTsPrf);
1108 rc = -3;
1109 except:
1110 g_oReporter.log(0, '** internal-error: Hit exception! %s' % (traceback.format_exc()), None, sTsPrf);
1111 rc = -2;
1112 else:
1113 g_oReporter.log(0, '** internal-error: No exception! %s'
1114 % (utils.getCallerName(iFrame=3)), utils.getCallerName(iFrame=3), sTsPrf);
1115 rc = -1;
1116
1117 finally:
1118 g_oLock.release();
1119 return rc;
1120
1121#
1122# The public Classes
1123#
1124class FileWrapper(object):
1125 """ File like class for TXS EXEC and similar. """
1126 def __init__(self, sPrefix):
1127 self.sPrefix = sPrefix;
1128
1129 def __del__(self):
1130 self.close();
1131
1132 def close(self):
1133 """ file.close """
1134 # Nothing to be done.
1135 return;
1136
1137 def read(self, cb):
1138 """file.read"""
1139 _ = cb;
1140 return "";
1141
1142 def write(self, sText):
1143 """file.write"""
1144 if isinstance(sText, array.array):
1145 try:
1146 sText = sText.tostring();
1147 except:
1148 pass;
1149 g_oLock.acquire();
1150 try:
1151 sTsPrf = utils.getTimePrefix();
1152 sCaller = utils.getCallerName();
1153 asLines = sText.splitlines();
1154 for sLine in asLines:
1155 g_oReporter.log(0, '%s: %s' % (self.sPrefix, sLine), sCaller, sTsPrf);
1156 except:
1157 traceback.print_exc();
1158 finally:
1159 g_oLock.release();
1160 return None;
1161
1162class FileWrapperTestPipe(object):
1163 """ File like class for the test pipe (TXS EXEC and similar). """
1164 def __init__(self):
1165 self.sPrefix = '';
1166 self.fStarted = False;
1167 self.fClosed = False;
1168 self.sTagBuffer = None;
1169
1170 def __del__(self):
1171 self.close();
1172
1173 def close(self):
1174 """ file.close """
1175 if self.fStarted is True and self.fClosed is False:
1176 self.fClosed = True;
1177 try: g_oReporter.subXmlEnd(self);
1178 except:
1179 try: traceback.print_exc();
1180 except: pass;
1181 return True;
1182
1183 def read(self, cb = None):
1184 """file.read"""
1185 _ = cb;
1186 return "";
1187
1188 def write(self, sText):
1189 """file.write"""
1190 # lazy start.
1191 if self.fStarted is not True:
1192 try:
1193 g_oReporter.subXmlStart(self);
1194 except:
1195 traceback.print_exc();
1196 self.fStarted = True;
1197
1198 if isinstance(sText, array.array):
1199 try:
1200 sText = sText.tostring();
1201 except:
1202 pass;
1203 try:
1204 g_oReporter.subXmlWrite(self, sText, utils.getCallerName());
1205 # Parse the supplied text and look for <Failed.../> tags to keep track of the
1206 # error counter. This is only a very lazy aproach.
1207 sText.strip();
1208 idxText = 0;
1209 while sText:
1210 if self.sTagBuffer is None:
1211 # Look for the start of a tag.
1212 idxStart = sText[idxText:].find('<');
1213 if idxStart != -1:
1214 # Look for the end of the tag.
1215 idxEnd = sText[idxStart:].find('>');
1216
1217 # If the end was found inside the current buffer, parse the line,
1218 # else we have to save it for later.
1219 if idxEnd != -1:
1220 idxEnd += idxStart + 1;
1221 self._processXmlElement(sText[idxStart:idxEnd]);
1222 idxText = idxEnd;
1223 else:
1224 self.sTagBuffer = sText[idxStart:];
1225 idxText = len(sText);
1226 else:
1227 idxText = len(sText);
1228 else:
1229 # Search for the end of the tag and parse the whole tag.
1230 idxEnd = sText[idxText:].find('>');
1231 if idxEnd != -1:
1232 idxEnd += idxStart + 1;
1233 self._processXmlElement(self.sTagBuffer + sText[idxText:idxEnd]);
1234 self.sTagBuffer = None;
1235 idxText = idxEnd;
1236 else:
1237 self.sTagBuffer = self.sTagBuffer + sText[idxText:];
1238 idxText = len(sText);
1239
1240 sText = sText[idxText:];
1241 sText = sText.lstrip();
1242 except:
1243 traceback.print_exc();
1244 return None;
1245
1246 def _processXmlElement(self, sElement):
1247 """
1248 Processes a complete XML tag (so far we only search for the Failed to tag
1249 to keep track of the error counter.
1250 """
1251 # Make sure we don't parse any space between < and the element name.
1252 sElement = sElement.strip();
1253
1254 # Find the end of the name
1255 idxEndName = sElement.find(' ');
1256 if idxEndName == -1:
1257 idxEndName = sElement.find('/');
1258 if idxEndName == -1:
1259 idxEndName = sElement.find('>');
1260
1261 if idxEndName != -1:
1262 if sElement[1:idxEndName] == 'Failed':
1263 g_oLock.acquire();
1264 try:
1265 g_oReporter.testIncErrors();
1266 finally:
1267 g_oLock.release();
1268 else:
1269 error('_processXmlElement(%s)' % sElement);
1270
1271
1272#
1273# The public APIs.
1274#
1275
1276def log(sText):
1277 """Writes the specfied text to the log."""
1278 g_oLock.acquire();
1279 try:
1280 rc = g_oReporter.log(1, sText, utils.getCallerName(), utils.getTimePrefix());
1281 except:
1282 rc = -1;
1283 finally:
1284 g_oLock.release();
1285 return rc;
1286
1287def logXcpt(sText=None, cFrames=1):
1288 """
1289 Log an exception, optionally with a preceeding message and more than one
1290 call frame.
1291 """
1292 return logXcptWorker(1, False, "", sText, cFrames);
1293
1294def log2(sText):
1295 """Log level 2: Writes the specfied text to the log."""
1296 g_oLock.acquire();
1297 try:
1298 rc = g_oReporter.log(2, sText, utils.getCallerName(), utils.getTimePrefix());
1299 except:
1300 rc = -1;
1301 finally:
1302 g_oLock.release();
1303 return rc;
1304
1305def log2Xcpt(sText=None, cFrames=1):
1306 """
1307 Log level 2: Log an exception, optionally with a preceeding message and
1308 more than one call frame.
1309 """
1310 return logXcptWorker(2, False, "", sText, cFrames);
1311
1312def maybeErr(fIsError, sText):
1313 """ Maybe error or maybe normal log entry. """
1314 if fIsError is True:
1315 return error(sText);
1316 return log(sText);
1317
1318def maybeErrXcpt(fIsError, sText=None, cFrames=1):
1319 """ Maybe error or maybe normal log exception entry. """
1320 if fIsError is True:
1321 return errorXcpt(sText, cFrames);
1322 return logXcpt(sText, cFrames);
1323
1324def maybeLog(fIsNotError, sText):
1325 """ Maybe error or maybe normal log entry. """
1326 if fIsNotError is not True:
1327 return error(sText);
1328 return log(sText);
1329
1330def maybeLogXcpt(fIsNotError, sText=None, cFrames=1):
1331 """ Maybe error or maybe normal log exception entry. """
1332 if fIsNotError is not True:
1333 return errorXcpt(sText, cFrames);
1334 return logXcpt(sText, cFrames);
1335
1336def error(sText):
1337 """
1338 Writes the specfied error message to the log.
1339
1340 This will add an error to the current test.
1341
1342 Always returns False for the convenience of methods returning boolean
1343 success indicators.
1344 """
1345 g_oLock.acquire();
1346 try:
1347 g_oReporter.testIncErrors();
1348 g_oReporter.log(0, '** error: %s' % (sText), utils.getCallerName(), utils.getTimePrefix());
1349 except:
1350 pass;
1351 finally:
1352 g_oLock.release();
1353 return False;
1354
1355def errorXcpt(sText=None, cFrames=1):
1356 """
1357 Log an error caused by an exception. If sText is given, it will preceed
1358 the exception information. cFrames can be used to display more stack.
1359
1360 This will add an error to the current test.
1361
1362 Always returns False for the convenience of methods returning boolean
1363 success indicators.
1364 """
1365 logXcptWorker(0, True, '** error: ', sText, cFrames);
1366 return False;
1367
1368def errorTimeout(sText):
1369 """
1370 Flags the current test as having timed out and writes the specified message to the log.
1371
1372 This will add an error to the current test.
1373
1374 Always returns False for the convenience of methods returning boolean
1375 success indicators.
1376 """
1377 g_oLock.acquire();
1378 try:
1379 g_oReporter.testSetTimedOut();
1380 g_oReporter.log(0, '** timeout-error: %s' % (sText), utils.getCallerName(), utils.getTimePrefix());
1381 except:
1382 pass;
1383 finally:
1384 g_oLock.release();
1385 return False;
1386
1387def fatal(sText):
1388 """
1389 Writes a fatal error to the log.
1390
1391 This will add an error to the current test.
1392
1393 Always returns False for the convenience of methods returning boolean
1394 success indicators.
1395 """
1396 g_oLock.acquire();
1397 try:
1398 g_oReporter.testIncErrors();
1399 g_oReporter.log(0, '** fatal error: %s' % (sText), utils.getCallerName(), utils.getTimePrefix());
1400 except:
1401 pass
1402 finally:
1403 g_oLock.release();
1404 return False;
1405
1406def fatalXcpt(sText=None, cFrames=1):
1407 """
1408 Log a fatal error caused by an exception. If sText is given, it will
1409 preceed the exception information. cFrames can be used to display more
1410 stack.
1411
1412 This will add an error to the current test.
1413
1414 Always returns False for the convenience of methods returning boolean
1415 success indicators.
1416 """
1417 logXcptWorker(0, True, "** fatal error: ", sText, cFrames);
1418 return False;
1419
1420def addLogFile(sFilename, sKind, sDescription = '', sAltName = None):
1421 """
1422 Adds the specified log file to the report if the file exists.
1423
1424 The sDescription is a free form description of the log file.
1425
1426 The sKind parameter is for adding some machine parsable hint what kind of
1427 log file this really is.
1428
1429 Returns True on success, False on failure (no ENOENT errors are logged).
1430 """
1431 sTsPrf = utils.getTimePrefix();
1432 sCaller = utils.getCallerName();
1433 fRc = False;
1434 if sAltName is None:
1435 sAltName = sFilename;
1436
1437 try:
1438 oSrcFile = utils.openNoInherit(sFilename, 'rb');
1439 except IOError as oXcpt:
1440 if oXcpt.errno != errno.ENOENT:
1441 logXcpt('addLogFile(%s,%s,%s)' % (sFilename, sDescription, sKind));
1442 else:
1443 logXcpt('addLogFile(%s,%s,%s) IOError' % (sFilename, sDescription, sKind));
1444 except:
1445 logXcpt('addLogFile(%s,%s,%s)' % (sFilename, sDescription, sKind));
1446 else:
1447 g_oLock.acquire();
1448 try:
1449 fRc = g_oReporter.addLogFile(oSrcFile, sFilename, sAltName, sDescription, sKind, sCaller, sTsPrf);
1450 finally:
1451 g_oLock.release();
1452 oSrcFile.close();
1453 return fRc;
1454
1455def addLogString(sLog, sLogName, sKind, sDescription = ''):
1456 """
1457 Adds the specified log string to the report.
1458
1459 The sLog parameter sets the name of the log file.
1460
1461 The sDescription is a free form description of the log file.
1462
1463 The sKind parameter is for adding some machine parsable hint what kind of
1464 log file this really is.
1465
1466 Returns True on success, False on failure (no ENOENT errors are logged).
1467 """
1468 sTsPrf = utils.getTimePrefix();
1469 sCaller = utils.getCallerName();
1470 fRc = False;
1471
1472 g_oLock.acquire();
1473 try:
1474 fRc = g_oReporter.addLogString(sLog, sLogName, sDescription, sKind, sCaller, sTsPrf);
1475 finally:
1476 g_oLock.release();
1477 return fRc;
1478
1479def isLocal():
1480 """Is this a local reporter?"""
1481 return g_oReporter.isLocal()
1482
1483def incVerbosity():
1484 """Increases the verbosity level."""
1485 return g_oReporter.incVerbosity()
1486
1487def incDebug():
1488 """Increases the debug level."""
1489 return g_oReporter.incDebug()
1490
1491def appendToProcessName(sAppend):
1492 """
1493 Appends sAppend to the base process name.
1494 Returns the new process name.
1495 """
1496 return g_oReporter.appendToProcessName(sAppend);
1497
1498def getErrorCount():
1499 """
1500 Get the current error count for the entire test run.
1501 """
1502 g_oLock.acquire();
1503 try:
1504 cErrors = g_oReporter.cErrors;
1505 finally:
1506 g_oLock.release();
1507 return cErrors;
1508
1509def doPollWork(sDebug = None):
1510 """
1511 This can be called from wait loops and similar to make the reporter call
1512 home with pending XML and such.
1513 """
1514 g_oReporter.doPollWork(sDebug);
1515 return None;
1516
1517
1518#
1519# Test reporting, a bit similar to RTTestI*.
1520#
1521
1522def testStart(sName):
1523 """
1524 Starts a new test (pushes it).
1525 """
1526 g_oLock.acquire();
1527 try:
1528 rc = g_oReporter.testStart(sName, utils.getCallerName());
1529 finally:
1530 g_oLock.release();
1531 return rc;
1532
1533def testValue(sName, sValue, sUnit):
1534 """
1535 Reports a benchmark value or something simiarlly useful.
1536 """
1537 g_oLock.acquire();
1538 try:
1539 rc = g_oReporter.testValue(sName, str(sValue), sUnit, utils.getCallerName());
1540 finally:
1541 g_oLock.release();
1542 return rc;
1543
1544def testFailure(sDetails):
1545 """
1546 Reports a failure.
1547 We count these calls and testDone will use them to report PASSED or FAILED.
1548
1549 Returns False so that a return False line can be saved.
1550 """
1551 g_oLock.acquire();
1552 try:
1553 g_oReporter.testFailure(sDetails, utils.getCallerName());
1554 finally:
1555 g_oLock.release();
1556 return False;
1557
1558def testFailureXcpt(sDetails = ''):
1559 """
1560 Reports a failure with exception.
1561 We count these calls and testDone will use them to report PASSED or FAILED.
1562
1563 Returns False so that a return False line can be saved.
1564 """
1565 # Extract exception info.
1566 try:
1567 oType, oValue, oTraceback = sys.exc_info();
1568 except:
1569 oType = oValue, oTraceback = None;
1570 if oType is not None:
1571 sCaller = utils.getCallerName(oTraceback.tb_frame);
1572 sXcpt = ' '.join(traceback.format_exception_only(oType, oValue));
1573 else:
1574 sCaller = utils.getCallerName();
1575 sXcpt = 'No exception at %s' % (sCaller,);
1576
1577 # Use testFailure to do the work.
1578 g_oLock.acquire();
1579 try:
1580 if sDetails == '':
1581 g_oReporter.testFailure('Exception: %s' % (sXcpt,), sCaller);
1582 else:
1583 g_oReporter.testFailure('%s: %s' % (sDetails, sXcpt), sCaller);
1584 finally:
1585 g_oLock.release();
1586 return False;
1587
1588def testDone(fSkipped = False):
1589 """
1590 Completes the current test (pops it), logging PASSED / FAILURE.
1591
1592 Returns a tuple with the name of the test and its error count.
1593 """
1594 g_oLock.acquire();
1595 try:
1596 rc = g_oReporter.testDone(fSkipped, utils.getCallerName());
1597 finally:
1598 g_oLock.release();
1599 return rc;
1600
1601def testErrorCount():
1602 """
1603 Gets the error count of the current test.
1604
1605 Returns the number of errors.
1606 """
1607 g_oLock.acquire();
1608 try:
1609 cErrors = g_oReporter.testErrorCount();
1610 finally:
1611 g_oLock.release();
1612 return cErrors;
1613
1614def testCleanup():
1615 """
1616 Closes all open tests with a generic error condition.
1617
1618 Returns True if no open tests, False if something had to be closed with failure.
1619 """
1620 g_oLock.acquire();
1621 try:
1622 fRc = g_oReporter.testCleanup(utils.getCallerName());
1623 g_oReporter.xmlFlush(fRetry = False, fForce = True);
1624 finally:
1625 g_oLock.release();
1626 return fRc;
1627
1628
1629#
1630# Sub XML stuff.
1631#
1632
1633def addSubXmlFile(sFilename):
1634 """
1635 Adds a sub-xml result file to the party.
1636 """
1637 fRc = False;
1638 try:
1639 oSrcFile = utils.openNoInherit(sFilename, 'r');
1640 except IOError as oXcpt:
1641 if oXcpt.errno != errno.ENOENT:
1642 logXcpt('addSubXmlFile(%s)' % (sFilename,));
1643 except:
1644 logXcpt('addSubXmlFile(%s)' % (sFilename,));
1645 else:
1646 try:
1647 oWrapper = FileWrapperTestPipe()
1648 oWrapper.write(oSrcFile.read());
1649 oWrapper.close();
1650 except:
1651 logXcpt('addSubXmlFile(%s)' % (sFilename,));
1652 oSrcFile.close();
1653
1654 return fRc;
1655
1656
1657#
1658# Other useful debugging tools.
1659#
1660
1661def logAllStacks(cFrames = None):
1662 """
1663 Logs the stacks of all python threads.
1664 """
1665 sTsPrf = utils.getTimePrefix();
1666 sCaller = utils.getCallerName();
1667 g_oLock.acquire();
1668
1669 cThread = 0;
1670 for idThread, oStack in sys._current_frames().items(): # >=2.5, a bit ugly - pylint: disable=W0212
1671 try:
1672 if cThread > 0:
1673 g_oReporter.log(1, '', sCaller, sTsPrf);
1674 g_oReporter.log(1, 'Thread %s (%#x)' % (idThread, idThread), sCaller, sTsPrf);
1675 try:
1676 asInfo = traceback.format_stack(oStack, cFrames);
1677 except:
1678 g_oReporter.log(1, ' Stack formatting failed w/ exception', sCaller, sTsPrf);
1679 else:
1680 for sInfo in asInfo:
1681 asLines = sInfo.splitlines();
1682 for sLine in asLines:
1683 g_oReporter.log(1, sLine, sCaller, sTsPrf);
1684 except:
1685 pass;
1686 cThread += 1;
1687
1688 g_oLock.release();
1689 return None;
1690
1691def checkTestManagerConnection():
1692 """
1693 Checks the connection to the test manager.
1694
1695 Returns True if the connection is fine, False if not, None if not remote
1696 reporter.
1697
1698 Note! This as the sideeffect of flushing XML.
1699 """
1700 g_oLock.acquire();
1701 try:
1702 fRc = g_oReporter.xmlFlush(fRetry = False, fForce = True);
1703 finally:
1704 g_oLock.release();
1705 return fRc;
1706
1707def flushall(fSkipXml = False):
1708 """
1709 Flushes all output streams, both standard and logger related.
1710 This may also push data to the remote test manager.
1711 """
1712 try: sys.stdout.flush();
1713 except: pass;
1714 try: sys.stderr.flush();
1715 except: pass;
1716
1717 if fSkipXml is not True:
1718 g_oLock.acquire();
1719 try:
1720 g_oReporter.xmlFlush(fRetry = False);
1721 finally:
1722 g_oLock.release();
1723
1724 return True;
1725
1726
1727#
1728# Module initialization.
1729#
1730
1731def _InitReporterModule():
1732 """
1733 Instantiate the test reporter.
1734 """
1735 global g_oReporter, g_sReporterName
1736
1737 g_sReporterName = os.getenv("TESTBOX_REPORTER", "local");
1738 if g_sReporterName == "local":
1739 g_oReporter = LocalReporter();
1740 elif g_sReporterName == "remote":
1741 g_oReporter = RemoteReporter(); # Correct, but still plain stupid. pylint: disable=redefined-variable-type
1742 else:
1743 print(os.path.basename(__file__) + ": Unknown TESTBOX_REPORTER value: '" + g_sReporterName + "'", file = sys.stderr);
1744 raise Exception("Unknown TESTBOX_REPORTER value '" + g_sReporterName + "'");
1745
1746if __name__ != "checker": # pychecker avoidance.
1747 _InitReporterModule();
1748
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