Home | History | Annotate | Download | only in testGenerator
      1 # Copyright (c) 2014-2015, Intel Corporation
      2 # All rights reserved.
      3 #
      4 # Redistribution and use in source and binary forms, with or without modification,
      5 # are permitted provided that the following conditions are met:
      6 #
      7 # 1. Redistributions of source code must retain the above copyright notice, this
      8 # list of conditions and the following disclaimer.
      9 #
     10 # 2. Redistributions in binary form must reproduce the above copyright notice,
     11 # this list of conditions and the following disclaimer in the documentation and/or
     12 # other materials provided with the distribution.
     13 #
     14 # 3. Neither the name of the copyright holder nor the names of its contributors
     15 # may be used to endorse or promote products derived from this software without
     16 # specific prior written permission.
     17 #
     18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
     19 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     20 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     21 # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
     22 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     23 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     24 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
     25 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     27 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28 
     29 import threading
     30 import subprocess
     31 import logging
     32 import signal
     33 import os
     34 
     35 
     36 class StreamLoggerThread(threading.Thread):
     37 
     38     """ File-like object used to log Popen stdout and stderr streams """
     39 
     40     def __init__(self, consoleLogger, level, name):
     41         """
     42             StreamLoggerThread Object initializer
     43 
     44             :param consoleLogger: console log handler
     45             :type consoleLogger: Handler
     46             :param level: desired logger level of the stream
     47             :type level: logging.level
     48             :param name: Thread name
     49             :type name: string
     50         """
     51         super().__init__()
     52 
     53         # Pipe to interact with subprocess
     54         self.__readFd, self.__writeFd = os.pipe()
     55 
     56         self.__logger = logging.getLogger(__name__)
     57         self.__logger.addHandler(consoleLogger)
     58 
     59         self.__level = level
     60 
     61         self.name = name
     62 
     63         # Start stream logging
     64         self.start()
     65 
     66     def fileno(self):
     67         """ Give Writing side of internal pipe to receive data """
     68 
     69         return self.__writeFd
     70 
     71     def run(self):
     72         """ Read the reading side of the pipe until EOF """
     73 
     74         with os.fdopen(self.__readFd) as stream:
     75             for line in stream:
     76                 self.__logger.log(self.__level, line.strip('\n'))
     77 
     78     def close(self):
     79         """ Close writing pipe side """
     80 
     81         os.close(self.__writeFd)
     82 
     83 
     84 class SubprocessLoggerThread(threading.Thread):
     85 
     86     """ This class is here to log long process stdout and stderr """
     87 
     88     # Event used to ask all SubprocessLoggerThread object to die
     89     __closeEvent = threading.Event()
     90 
     91     def __init__(self, cmd, consoleLogger):
     92         """
     93             SubprocessLoggerThread Object initializer
     94 
     95             :param cmd: command to launch
     96             :type cmd: list
     97             :param consoleLogger: console log handler
     98             :type consoleLogger: Handler
     99         """
    100 
    101         super().__init__()
    102 
    103         self.__cmd = cmd
    104         self.__subProc = None
    105 
    106         self.name = "Thread : " + ' '.join(cmd)
    107 
    108         self.__consoleLogger = consoleLogger
    109 
    110         # Default logging level
    111         self._stdOutLogLevel = logging.DEBUG
    112 
    113     @classmethod
    114     def closeAll(cls):
    115         """ Set the closeEvent to ask the thread to die  """
    116         cls.__closeEvent.set()
    117 
    118     def __cleanup(self):
    119         """
    120             Close properly the child with SIGINT.
    121 
    122             The signal is sended to all the group to kill
    123             subprocess launched by Popen
    124         """
    125         os.killpg(self.__subProc.pid, signal.SIGINT)
    126 
    127     def __subProcPreExec(self):
    128         """
    129             Make Popen object a Group leader.
    130 
    131             Avoid subprocess to receive signal destinated
    132             to the MainThread.
    133         """
    134         os.setpgrp()
    135 
    136     def run(self):
    137         """ Create Popen object and manage it """
    138 
    139         # Logging threaded file-object
    140         stdOutLogger = StreamLoggerThread(
    141             self.__consoleLogger,
    142             self._stdOutLogLevel,
    143             self.name + "STDOUT")
    144         stdErrLogger = StreamLoggerThread(
    145             self.__consoleLogger,
    146             logging.ERROR,
    147             self.name + "STDERR")
    148 
    149         # Logging stdout and stderr through objects
    150         self.__subProc = subprocess.Popen(
    151             [os.getenv("SHELL"), "-c", ' '.join(self.__cmd)],
    152             bufsize=1,
    153             stdout=stdOutLogger,
    154             stderr=stdErrLogger,
    155             preexec_fn=self.__subProcPreExec,
    156             shell=False)
    157 
    158         # Waiting process close or closing order
    159         while True:
    160             try:
    161                 # We end the thread if we are requested to do so
    162                 if SubprocessLoggerThread.__closeEvent.wait(0.01):
    163                     self.__cleanup()
    164                     break
    165 
    166                 # or if the subprocess is dead
    167                 if self.__subProc.poll() is not None:
    168                     break
    169             except KeyboardInterrupt:
    170                 continue
    171 
    172         # Close pipes
    173         if stdOutLogger.is_alive():
    174             stdOutLogger.close()
    175         if stdErrLogger.is_alive():
    176             stdErrLogger.close()
    177 
    178 
    179 class ScriptLoggerThread(SubprocessLoggerThread):
    180 
    181     """ This class is used to log script subprocess """
    182 
    183     def __init__(self, cmd, consoleLogger):
    184         """
    185             ScriptLoggerThread Object initializer
    186 
    187             :param cmd: command to launch
    188             :type cmd: list
    189             :param consoleLogger: console log handler
    190             :type consoleLogger: Handler
    191         """
    192         super().__init__(cmd, consoleLogger)
    193 
    194         # Script logging level
    195         self._stdOutLogLevel = logging.INFO
    196 
    197     @classmethod
    198     def getRunningInstances(cls):
    199         """
    200             Running ScriptLoggerThread instances getter
    201 
    202             :return: The list of running ScriptLoggerThread instances
    203             :rtype: list
    204         """
    205         return [t for t in threading.enumerate() if isinstance(t, cls)]
    206