Example #1
0
    def start(self):
        assert self.process == None or self.process.poll() != None

        # Reset the test log
        self.testLog = []

        popenArgs = [self.binary]
        popenArgs.extend(self.args)

        self.process = subprocess.Popen(popenArgs,
                                        stdin=subprocess.PIPE,
                                        stdout=subprocess.PIPE,
                                        stderr=subprocess.PIPE,
                                        cwd=self.cwd,
                                        env=self.env,
                                        universal_newlines=True)

        # This queue is used to queue up responses that should be directly processed
        # by this class rather than being logged.
        self.responseQueue = Queue.Queue()

        self.outCollector = StreamCollector(self.process.stdout,
                                            self.responseQueue,
                                            logResponses=False,
                                            maxBacklog=256)
        self.errCollector = StreamCollector(self.process.stderr,
                                            self.responseQueue,
                                            logResponses=False,
                                            maxBacklog=256)

        # Anything prefixed with "SPFP: " will be directly forwarded to us
        self.outCollector.addResponsePrefix("SPFP: ")
        self.errCollector.addResponsePrefix("SPFP: ")

        self.outCollector.start()
        self.errCollector.start()

        try:
            self.process.stdin.write('selftest\n')
        except IOError:
            raise RuntimeError(
                "SPFP Error: Selftest failed, application did not start properly."
            )

        try:
            response = self.responseQueue.get(block=True,
                                              timeout=self.processingTimeout)
        except Queue.Empty:
            raise RuntimeError("SPFP Error: Selftest failed, no response.")

        if response != "PASSED":
            raise RuntimeError(
                "SPFP Error: Selftest failed, unsupported application response: %s"
                % response)
    def start(self):
        assert self.process == None or self.process.poll() != None
        
        # Reset the test log
        self.testLog = []
        
        popenArgs = [ self.binary ]
        popenArgs.extend(self.args)
        
        self.process = subprocess.Popen(
                         popenArgs,
                         stdin=subprocess.PIPE,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE,
                         cwd=self.cwd,
                         env=self.env,
                         universal_newlines=True
                        )
        
        # This queue is used to queue up responses that should be directly processed
        # by this class rather than being logged.
        self.responseQueue = Queue.Queue()
        
        self.outCollector = StreamCollector(self.process.stdout, self.responseQueue, logResponses=False, maxBacklog=256)
        self.errCollector = StreamCollector(self.process.stderr, self.responseQueue, logResponses=False, maxBacklog=256)
        
        # Anything prefixed with "SPFP: " will be directly forwarded to us
        self.outCollector.addResponsePrefix("SPFP: ")
        self.errCollector.addResponsePrefix("SPFP: ")
        
        self.outCollector.start()
        self.errCollector.start()
        
        try:
            self.process.stdin.write('selftest\n')
        except IOError:
            raise RuntimeError("SPFP Error: Selftest failed, application did not start properly.")

        try:
            response = self.responseQueue.get(block=True, timeout=self.processingTimeout)
        except Queue.Empty:
            raise RuntimeError("SPFP Error: Selftest failed, no response.")
        
        if response != "PASSED":
            raise RuntimeError("SPFP Error: Selftest failed, unsupported application response: %s" % response)
Example #3
0
class SimplePersistentApplication(PersistentApplication):
    def __init__(self, binary, args=None, env=None, cwd=None):
        PersistentApplication.__init__(self, binary, args, env, cwd)

        # How many seconds to give the program for processing out input
        self.processingTimeout = 10

    def start(self):
        assert self.process == None or self.process.poll() != None

        # Reset the test log
        self.testLog = []

        popenArgs = [self.binary]
        popenArgs.extend(self.args)

        self.process = subprocess.Popen(popenArgs,
                                        stdin=subprocess.PIPE,
                                        stdout=subprocess.PIPE,
                                        stderr=subprocess.PIPE,
                                        cwd=self.cwd,
                                        env=self.env,
                                        universal_newlines=True)

        # This queue is used to queue up responses that should be directly processed
        # by this class rather than being logged.
        self.responseQueue = Queue.Queue()

        self.outCollector = StreamCollector(self.process.stdout,
                                            self.responseQueue,
                                            logResponses=False,
                                            maxBacklog=256)
        self.errCollector = StreamCollector(self.process.stderr,
                                            self.responseQueue,
                                            logResponses=False,
                                            maxBacklog=256)

        # Anything prefixed with "SPFP: " will be directly forwarded to us
        self.outCollector.addResponsePrefix("SPFP: ")
        self.errCollector.addResponsePrefix("SPFP: ")

        self.outCollector.start()
        self.errCollector.start()

        try:
            self.process.stdin.write('selftest\n')
        except IOError:
            raise RuntimeError(
                "SPFP Error: Selftest failed, application did not start properly."
            )

        try:
            response = self.responseQueue.get(block=True,
                                              timeout=self.processingTimeout)
        except Queue.Empty:
            raise RuntimeError("SPFP Error: Selftest failed, no response.")

        if response != "PASSED":
            raise RuntimeError(
                "SPFP Error: Selftest failed, unsupported application response: %s"
                % response)

    def stop(self):
        self._terminateProcess()

        # Ensure we leave no dangling threads when stopping
        self.outCollector.join()
        self.errCollector.join()

        # Make the output available
        self.stdout = self.outCollector.output
        self.stderr = self.errCollector.output

    def runTest(self, test):
        if self.process == None or self.process.poll() != None:
            self.start()

        self.testLog.append(test)
        self.process.stdin.write('%s\n' % test)

        try:
            response = self.responseQueue.get(block=True,
                                              timeout=self.processingTimeout)
        except Queue.Empty:
            if self.process.poll() == None:
                # The process is still running, force it to stop and return timeout code
                self.stop()
                return ApplicationStatus.TIMEDOUT
            else:
                # The process has exited. We need to check if it crashed, but first we
                # call stop to join our collector threads.
                self.stop()

                if self.process.returncode < 0:
                    crashSignals = [
                        # POSIX.1-1990 signals
                        signal.SIGILL,
                        signal.SIGABRT,
                        signal.SIGFPE,
                        signal.SIGSEGV,
                        # SUSv2 / POSIX.1-2001 signals
                        signal.SIGBUS,
                        signal.SIGSYS,
                        signal.SIGTRAP,
                    ]

                    for crashSignal in crashSignals:
                        if self.process.returncode == -crashSignal:
                            return ApplicationStatus.CRASHED

                    # The application was terminated by a signal, but not by one of the listed signals.
                    # We consider this a fatal error. Either the signal should be supported here, or the
                    # process is being terminated by something else, making the testing unreliable.
                    #
                    # TODO: This could be triggered by the Linux kernel OOM killer
                    raise RuntimeError(
                        "SPFP Error: Application terminated with signal: %s" %
                        self.process.returncode)
                else:
                    # The application exited, but didn't send us any message before doing so. We consider this
                    # a protocol violation and raise an exception.
                    raise RuntimeError(
                        "SPFP Error: Application exited without message. Exitcode: %s"
                        % self.process.returncode)

        if response == 'OK':
            return ApplicationStatus.OK
        elif response == 'ERROR':
            return ApplicationStatus.ERROR

        raise RuntimeError("SPFP Error: Unsupported application response: %s" %
                           response)

    def _terminateProcess(self):
        if self.process:
            if self.process.poll() == None:
                # Try to terminate the process gracefully first
                self.process.terminate()

                # Emulate a wait() with timeout. Because wait() having
                # a timeout would be way too easy, wouldn't it? -.-
                (maxSleepTime, pollInterval) = (3, 0.2)
                while self.process.poll() == None and maxSleepTime > 0:
                    maxSleepTime -= pollInterval
                    time.sleep(pollInterval)

                # Process is still alive, kill it and wait
                if self.process.poll() == None:
                    self.process.kill()
                    self.process.wait()
    def start(self, test=None):
        assert self.process is None or self.process.poll() is not None

        # Reset the test log
        self.testLog = []

        if self.persistentMode == PersistentMode.NONE:
            assert test is not None
            if self.inputFile:
                self._write_log_test(test)
        else:
            # We should only get a test here if we don't run in persistent mode
            # at all. Otherwise, all tests should go through the runTest method.
            assert test is None

        popenArgs = [self.binary]
        popenArgs.extend(self.args)

        self.process = subprocess.Popen(
            popenArgs,
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            cwd=self.cwd,
            env=self.env,
            universal_newlines=True
        )

        # This queue is used to queue up responses that should be directly processed
        # by this class rather than being logged.
        self.responseQueue = queue.Queue()

        self.outCollector = StreamCollector(self.process.stdout, self.responseQueue, logResponses=False, maxBacklog=256)
        self.errCollector = StreamCollector(self.process.stderr, self.responseQueue, logResponses=False, maxBacklog=256)

        # Anything prefixed with "SPFP: " will be directly forwarded to us.
        # This is helpful for debugging, even with other PersistentMode settings.
        self.outCollector.addResponsePrefix("SPFP: ")
        self.errCollector.addResponsePrefix("SPFP: ")

        self.outCollector.start()
        self.errCollector.start()

        if self.persistentMode == PersistentMode.SPFP:
            try:
                print("%sspfp-selftest%s" % (self.spfpPrefix, self.spfpSuffix), file=self.process.stdin)
            except IOError:
                raise RuntimeError("SPFP Error: Selftest failed, application did not start properly.")

            try:
                response = self.responseQueue.get(block=True, timeout=self.processingTimeout)
            except queue.Empty:
                raise RuntimeError("SPFP Error: Selftest failed, no response.")

            if response != "PASSED":
                raise RuntimeError("SPFP Error: Selftest failed, unsupported application response: %s" % response)
        elif self.persistentMode == PersistentMode.SIGSTOP:
            if not self._wait_child_stopped():
                raise RuntimeError("SIGSTOP Error: Failed to wait for application to stop itself after startup")

            if self.process.poll() is not None:
                raise RuntimeError("SIGSTOP Error: Application terminated instead of stopping itself")
        else:
            if not self.inputFile:
                self._write_log_test(test)

            # Assume PersistentMode.NONE and expect the process to exit now
            (maxSleepTime, pollInterval) = (self.processingTimeout, 0.2)
            while self.process.poll() is None and maxSleepTime > 0:
                maxSleepTime -= pollInterval
                time.sleep(pollInterval)

            ret = ApplicationStatus.OK

            # Process is still alive, consider this a timeout
            if self.process.poll() is None:
                ret = ApplicationStatus.TIMEDOUT
            elif self._crashed():
                ret = ApplicationStatus.CRASHED
            elif self.process.returncode:
                ret = ApplicationStatus.ERROR

            # Stop threads, make output available.
            # Also terminates the process in case of a timeout.
            self.stop()

            return ret
class SimplePersistentApplication(PersistentApplication):
    def __init__(self, binary, args=None, env=None, cwd=None, persistentMode=PersistentMode.NONE,
                 processingTimeout=10, inputFile=None):
        PersistentApplication.__init__(self, binary, args, env, cwd, persistentMode,
                                       processingTimeout, inputFile)

        # Used to store the second return value if waitpid, which has the real exit code
        self.childExit = None

        # These will hold our StreamCollectors for stdout/err
        self.outCollector = None
        self.errCollector = None

    def _write_log_test(self, test):
        self.testLog.append(test)

        if self.inputFile:
            with open(self.inputFile, 'w') as inputFileFd:
                inputFileFd.write(test)
        elif self.persistentMode == PersistentMode.SPFP:
            # This won't work with pure binary data, but SPFP mode isn't suitable for that in general
            print(test, file=self.process.stdin)
            print("%sspfp-endofdata%s" % (self.spfpPrefix, self.spfpSuffix), file=self.process.stdin)
        elif self.persistentMode == PersistentMode.SIGSTOP:
            # Shameless copycat, oh hai lcamtuf ;)
            os.ftruncate(self.process.stdin, len(test))
            os.lseek(self.process.stdin, 0, os.SEEK_SET)
            self.process.stdin.write(test)
            self.process.stdin.flush()
        else:
            self.process.stdin.write(test)
            self.process.stdin.close()

    def _wait_child_stopped(self):
        monitor = WaitpidMonitor(self.process.pid, os.WUNTRACED)
        monitor.start()
        monitor.join(self.processingTimeout)

        if monitor.isAlive():
            # Timed out
            return False

        # Save the exit result returned by waitpid() as we need it
        # in case the process crashed or otherwise exited unexpectedly
        self.childExit = monitor.childExit

        return True

    def start(self, test=None):
        assert self.process is None or self.process.poll() is not None

        # Reset the test log
        self.testLog = []

        if self.persistentMode == PersistentMode.NONE:
            assert test is not None
            if self.inputFile:
                self._write_log_test(test)
        else:
            # We should only get a test here if we don't run in persistent mode
            # at all. Otherwise, all tests should go through the runTest method.
            assert test is None

        popenArgs = [self.binary]
        popenArgs.extend(self.args)

        self.process = subprocess.Popen(
            popenArgs,
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            cwd=self.cwd,
            env=self.env,
            universal_newlines=True
        )

        # This queue is used to queue up responses that should be directly processed
        # by this class rather than being logged.
        self.responseQueue = queue.Queue()

        self.outCollector = StreamCollector(self.process.stdout, self.responseQueue, logResponses=False, maxBacklog=256)
        self.errCollector = StreamCollector(self.process.stderr, self.responseQueue, logResponses=False, maxBacklog=256)

        # Anything prefixed with "SPFP: " will be directly forwarded to us.
        # This is helpful for debugging, even with other PersistentMode settings.
        self.outCollector.addResponsePrefix("SPFP: ")
        self.errCollector.addResponsePrefix("SPFP: ")

        self.outCollector.start()
        self.errCollector.start()

        if self.persistentMode == PersistentMode.SPFP:
            try:
                print("%sspfp-selftest%s" % (self.spfpPrefix, self.spfpSuffix), file=self.process.stdin)
            except IOError:
                raise RuntimeError("SPFP Error: Selftest failed, application did not start properly.")

            try:
                response = self.responseQueue.get(block=True, timeout=self.processingTimeout)
            except queue.Empty:
                raise RuntimeError("SPFP Error: Selftest failed, no response.")

            if response != "PASSED":
                raise RuntimeError("SPFP Error: Selftest failed, unsupported application response: %s" % response)
        elif self.persistentMode == PersistentMode.SIGSTOP:
            if not self._wait_child_stopped():
                raise RuntimeError("SIGSTOP Error: Failed to wait for application to stop itself after startup")

            if self.process.poll() is not None:
                raise RuntimeError("SIGSTOP Error: Application terminated instead of stopping itself")
        else:
            if not self.inputFile:
                self._write_log_test(test)

            # Assume PersistentMode.NONE and expect the process to exit now
            (maxSleepTime, pollInterval) = (self.processingTimeout, 0.2)
            while self.process.poll() is None and maxSleepTime > 0:
                maxSleepTime -= pollInterval
                time.sleep(pollInterval)

            ret = ApplicationStatus.OK

            # Process is still alive, consider this a timeout
            if self.process.poll() is None:
                ret = ApplicationStatus.TIMEDOUT
            elif self._crashed():
                ret = ApplicationStatus.CRASHED
            elif self.process.returncode:
                ret = ApplicationStatus.ERROR

            # Stop threads, make output available.
            # Also terminates the process in case of a timeout.
            self.stop()

            return ret

    def stop(self):
        self._terminateProcess()

        # Ensure we leave no dangling threads when stopping
        if self.outCollector is not None:
            # errCollector is expected to be set when outCollector is
            self.outCollector.join()
            self.errCollector.join()

            # Make the output available
            self.stdout = self.outCollector.output
            self.stderr = self.errCollector.output

    def runTest(self, test):
        if self.process is None or self.process.poll() is not None:
            self.start()

        # Write test data and also log it
        self._write_log_test(test)

        if self.persistentMode == PersistentMode.SPFP:
            try:
                response = self.responseQueue.get(block=True, timeout=self.processingTimeout)
            except queue.Empty:
                if self.process.poll() is None:
                    # The process is still running, force it to stop and return timeout code
                    self.stop()
                    return ApplicationStatus.TIMEDOUT
                else:
                    # The process has exited. We need to check if it crashed, but first we
                    # call stop to join our collector threads.
                    self.stop()

                    if self._crashed():
                        return ApplicationStatus.CRASHED
                    elif self.process.returncode < 0:
                        # The application was terminated by a signal, but not by one of the listed signals.
                        # We consider this a fatal error. Either the signal should be supported here, or the
                        # process is being terminated by something else, making the testing unreliable.
                        #
                        # TODO: This could be triggered by the Linux kernel OOM killer
                        raise RuntimeError("SPFP Error: Application terminated with signal: %s" %
                                           self.process.returncode)
                    else:
                        # The application exited, but didn't send us any message before doing so. We consider this
                        # a protocol violation and raise an exception.
                        raise RuntimeError("SPFP Error: Application exited without message. Exitcode: %s" %
                                           self.process.returncode)

            # Update stdout/err available for the last run
            self.stdout = self.outCollector.output
            self.stderr = self.errCollector.output

            if response == 'OK':
                return ApplicationStatus.OK
            elif response == 'ERROR':
                return ApplicationStatus.ERROR

            raise RuntimeError("SPFP Error: Unsupported application response: %s" % response)
        elif self.persistentMode == PersistentMode.SIGSTOP:
            # Resume the process
            os.kill(self.process.pid, signal.SIGCONT)

            # Wait for process to stop itself again
            if not self._wait_child_stopped():
                # The process is still running, force it to stop and return timeout code
                self.stop()
                return ApplicationStatus.TIMEDOUT

            # Update stdout/err available for the last run
            self.stdout = self.outCollector.output
            self.stderr = self.errCollector.output

            if self.process.poll() is not None:
                exitCode = self.childExit >> 8
                signalNum = self.childExit & 0xFF

                if exitCode:
                    self.process.returncode = exitCode
                else:
                    self.process.returncode = -signalNum

                if self._crashed():
                    return ApplicationStatus.CRASHED
                else:
                    return ApplicationStatus.ERROR

            return ApplicationStatus.OK

    def _terminateProcess(self):
        if self.process:
            if self.process.poll() is None:
                # Try to terminate the process gracefully first
                self.process.terminate()

                # Emulate a wait() with timeout. Because wait() having
                # a timeout would be way too easy, wouldn't it? -.-
                (maxSleepTime, pollInterval) = (3, 0.2)
                while self.process.poll() is None and maxSleepTime > 0:
                    maxSleepTime -= pollInterval
                    time.sleep(pollInterval)

                # Process is still alive, kill it and wait
                if self.process.poll() is None:
                    self.process.kill()
                    self.process.wait()
    def start(self, test=None):
        assert self.process is None or self.process.poll() is not None

        # Reset the test log
        self.testLog = []

        if self.persistentMode == PersistentMode.NONE:
            assert test is not None
            if self.inputFile:
                self._write_log_test(test)
        else:
            # We should only get a test here if we don't run in persistent mode
            # at all. Otherwise, all tests should go through the runTest method.
            assert test is None

        popenArgs = [ self.binary ]
        popenArgs.extend(self.args)

        self.process = subprocess.Popen(
                         popenArgs,
                         stdin=subprocess.PIPE,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE,
                         cwd=self.cwd,
                         env=self.env,
                         universal_newlines=True
                        )

        # This queue is used to queue up responses that should be directly processed
        # by this class rather than being logged.
        self.responseQueue = Queue.Queue()

        self.outCollector = StreamCollector(self.process.stdout, self.responseQueue, logResponses=False, maxBacklog=256)
        self.errCollector = StreamCollector(self.process.stderr, self.responseQueue, logResponses=False, maxBacklog=256)

        # Anything prefixed with "SPFP: " will be directly forwarded to us.
        # This is helpful for debugging, even with other PersistentMode settings.
        self.outCollector.addResponsePrefix("SPFP: ")
        self.errCollector.addResponsePrefix("SPFP: ")

        self.outCollector.start()
        self.errCollector.start()

        if self.persistentMode == PersistentMode.SPFP:
            try:
                print("%sspfp-selftest%s" % (self.spfpPrefix, self.spfpSuffix), file=self.process.stdin)
            except IOError:
                raise RuntimeError("SPFP Error: Selftest failed, application did not start properly.")

            try:
                response = self.responseQueue.get(block=True, timeout=self.processingTimeout)
            except Queue.Empty:
                raise RuntimeError("SPFP Error: Selftest failed, no response.")

            if response != "PASSED":
                raise RuntimeError("SPFP Error: Selftest failed, unsupported application response: %s" % response)
        elif self.persistentMode == PersistentMode.SIGSTOP:
            if not self._wait_child_stopped():
                raise RuntimeError("SIGSTOP Error: Failed to wait for application to stop itself after startup")

            if self.process.poll() is not None:
                raise RuntimeError("SIGSTOP Error: Application terminated instead of stopping itself")
        else:
            if not self.inputFile:
                self._write_log_test(test)

            # Assume PersistentMode.NONE and expect the process to exit now
            (maxSleepTime, pollInterval) = (self.processingTimeout, 0.2)
            while self.process.poll() == None and maxSleepTime > 0:
                maxSleepTime -= pollInterval
                time.sleep(pollInterval)

            ret = ApplicationStatus.OK

            # Process is still alive, consider this a timeout
            if self.process.poll() == None:
                ret = ApplicationStatus.TIMEDOUT
            elif self._crashed():
                ret = ApplicationStatus.CRASHED
            elif self.process.returncode:
                ret = ApplicationStatus.ERROR

            # Stop threads, make output available.
            # Also terminates the process in case of a timeout.
            self.stop()

            return ret
class SimplePersistentApplication(PersistentApplication):
    def __init__(self, binary, args=None, env=None, cwd=None, persistentMode=PersistentMode.NONE,
                 processingTimeout=10, inputFile=None):
        PersistentApplication.__init__(self, binary, args, env, cwd, persistentMode,
                                       processingTimeout, inputFile)

        # Used to store the second return value if waitpid, which has the real exit code
        self.childExit = None

        # These will hold our StreamCollectors for stdout/err
        self.outCollector = None
        self.errCollector = None

    def _write_log_test(self, test):
        self.testLog.append(test)

        if self.inputFile:
            with open(self.inputFile, 'w') as inputFileFd:
                inputFileFd.write(test)
        elif self.persistentMode == PersistentMode.SPFP:
            # This won't work with pure binary data, but SPFP mode isn't suitable for that in general
            print(test, file=self.process.stdin)
            print("%sspfp-endofdata%s" % (self.spfpPrefix, self.spfpSuffix), file=self.process.stdin)
        elif self.persistentMode == PersistentMode.SIGSTOP:
            # Shameless copycat, oh hai lcamtuf ;)
            os.ftruncate(self.process.stdin, len(test))
            os.lseek(self.process.stdin, 0, os.SEEK_SET)
            self.process.stdin.write(test)
            self.process.stdin.flush()
        else:
            self.process.stdin.write(test)
            self.process.stdin.close()

    def _wait_child_stopped(self):
        monitor = WaitpidMonitor(self.process.pid, os.WUNTRACED)
        monitor.start()
        monitor.join(self.processingTimeout)

        if monitor.isAlive():
            # Timed out
            return False

        # Save the exit result returned by waitpid() as we need it
        # in case the process crashed or otherwise exited unexpectedly
        self.childExit = monitor.childExit

        return True

    def start(self, test=None):
        assert self.process is None or self.process.poll() is not None

        # Reset the test log
        self.testLog = []

        if self.persistentMode == PersistentMode.NONE:
            assert test is not None
            if self.inputFile:
                self._write_log_test(test)
        else:
            # We should only get a test here if we don't run in persistent mode
            # at all. Otherwise, all tests should go through the runTest method.
            assert test is None

        popenArgs = [ self.binary ]
        popenArgs.extend(self.args)

        self.process = subprocess.Popen(
                         popenArgs,
                         stdin=subprocess.PIPE,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE,
                         cwd=self.cwd,
                         env=self.env,
                         universal_newlines=True
                        )

        # This queue is used to queue up responses that should be directly processed
        # by this class rather than being logged.
        self.responseQueue = Queue.Queue()

        self.outCollector = StreamCollector(self.process.stdout, self.responseQueue, logResponses=False, maxBacklog=256)
        self.errCollector = StreamCollector(self.process.stderr, self.responseQueue, logResponses=False, maxBacklog=256)

        # Anything prefixed with "SPFP: " will be directly forwarded to us.
        # This is helpful for debugging, even with other PersistentMode settings.
        self.outCollector.addResponsePrefix("SPFP: ")
        self.errCollector.addResponsePrefix("SPFP: ")

        self.outCollector.start()
        self.errCollector.start()

        if self.persistentMode == PersistentMode.SPFP:
            try:
                print("%sspfp-selftest%s" % (self.spfpPrefix, self.spfpSuffix), file=self.process.stdin)
            except IOError:
                raise RuntimeError("SPFP Error: Selftest failed, application did not start properly.")

            try:
                response = self.responseQueue.get(block=True, timeout=self.processingTimeout)
            except Queue.Empty:
                raise RuntimeError("SPFP Error: Selftest failed, no response.")

            if response != "PASSED":
                raise RuntimeError("SPFP Error: Selftest failed, unsupported application response: %s" % response)
        elif self.persistentMode == PersistentMode.SIGSTOP:
            if not self._wait_child_stopped():
                raise RuntimeError("SIGSTOP Error: Failed to wait for application to stop itself after startup")

            if self.process.poll() is not None:
                raise RuntimeError("SIGSTOP Error: Application terminated instead of stopping itself")
        else:
            if not self.inputFile:
                self._write_log_test(test)

            # Assume PersistentMode.NONE and expect the process to exit now
            (maxSleepTime, pollInterval) = (self.processingTimeout, 0.2)
            while self.process.poll() == None and maxSleepTime > 0:
                maxSleepTime -= pollInterval
                time.sleep(pollInterval)

            ret = ApplicationStatus.OK

            # Process is still alive, consider this a timeout
            if self.process.poll() == None:
                ret = ApplicationStatus.TIMEDOUT
            elif self._crashed():
                ret = ApplicationStatus.CRASHED
            elif self.process.returncode:
                ret = ApplicationStatus.ERROR

            # Stop threads, make output available.
            # Also terminates the process in case of a timeout.
            self.stop()

            return ret


    def stop(self):
        self._terminateProcess()

        # Ensure we leave no dangling threads when stopping
        if self.outCollector is not None:
            # errCollector is expected to be set when outCollector is
            self.outCollector.join()
            self.errCollector.join()

            # Make the output available
            self.stdout = self.outCollector.output
            self.stderr = self.errCollector.output

    def runTest(self, test):
        if self.process == None or self.process.poll() != None:
            self.start()

        # Write test data and also log it
        self._write_log_test(test)

        if self.persistentMode == PersistentMode.SPFP:
            try:
                response = self.responseQueue.get(block=True, timeout=self.processingTimeout)
            except Queue.Empty:
                if self.process.poll() == None:
                    # The process is still running, force it to stop and return timeout code
                    self.stop()
                    return ApplicationStatus.TIMEDOUT
                else:
                    # The process has exited. We need to check if it crashed, but first we
                    # call stop to join our collector threads.
                    self.stop()

                    if self._crashed():
                        return ApplicationStatus.CRASHED
                    elif self.process.returncode < 0:
                        # The application was terminated by a signal, but not by one of the listed signals.
                        # We consider this a fatal error. Either the signal should be supported here, or the
                        # process is being terminated by something else, making the testing unreliable.
                        #
                        # TODO: This could be triggered by the Linux kernel OOM killer
                        raise RuntimeError("SPFP Error: Application terminated with signal: %s" % self.process.returncode)
                    else:
                        # The application exited, but didn't send us any message before doing so. We consider this
                        # a protocol violation and raise an exception.
                        raise RuntimeError("SPFP Error: Application exited without message. Exitcode: %s" % self.process.returncode)

            # Update stdout/err available for the last run
            self.stdout = self.outCollector.output
            self.stderr = self.errCollector.output

            if response == 'OK':
                return ApplicationStatus.OK
            elif response == 'ERROR':
                return ApplicationStatus.ERROR

            raise RuntimeError("SPFP Error: Unsupported application response: %s" % response)
        elif self.persistentMode == PersistentMode.SIGSTOP:
            # Resume the process
            os.kill(self.process.pid, signal.SIGCONT)

            # Wait for process to stop itself again
            if not self._wait_child_stopped():
                # The process is still running, force it to stop and return timeout code
                self.stop()
                return ApplicationStatus.TIMEDOUT

            # Update stdout/err available for the last run
            self.stdout = self.outCollector.output
            self.stderr = self.errCollector.output

            if self.process.poll() is not None:
                exitCode = self.childExit >> 8
                signalNum = self.childExit & 0xFF

                if exitCode:
                    self.process.returncode = exitCode
                else:
                    self.process.returncode = -signalNum

                if self._crashed():
                    return ApplicationStatus.CRASHED
                else:
                    return ApplicationStatus.ERROR

            return ApplicationStatus.OK

    def _terminateProcess(self):
        if self.process:
            if self.process.poll() == None:
                # Try to terminate the process gracefully first
                self.process.terminate()

                # Emulate a wait() with timeout. Because wait() having
                # a timeout would be way too easy, wouldn't it? -.-
                (maxSleepTime, pollInterval) = (3, 0.2)
                while self.process.poll() == None and maxSleepTime > 0:
                    maxSleepTime -= pollInterval
                    time.sleep(pollInterval)

                # Process is still alive, kill it and wait
                if self.process.poll() == None:
                    self.process.kill()
                    self.process.wait()
class SimplePersistentApplication(PersistentApplication):
    def __init__(self, binary, args=None, env=None, cwd=None):
        PersistentApplication.__init__(self, binary, args, env, cwd)
        
        # How many seconds to give the program for processing out input
        self.processingTimeout = 10
        
    def start(self):
        assert self.process == None or self.process.poll() != None
        
        # Reset the test log
        self.testLog = []
        
        popenArgs = [ self.binary ]
        popenArgs.extend(self.args)
        
        self.process = subprocess.Popen(
                         popenArgs,
                         stdin=subprocess.PIPE,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE,
                         cwd=self.cwd,
                         env=self.env,
                         universal_newlines=True
                        )
        
        # This queue is used to queue up responses that should be directly processed
        # by this class rather than being logged.
        self.responseQueue = Queue.Queue()
        
        self.outCollector = StreamCollector(self.process.stdout, self.responseQueue, logResponses=False, maxBacklog=256)
        self.errCollector = StreamCollector(self.process.stderr, self.responseQueue, logResponses=False, maxBacklog=256)
        
        # Anything prefixed with "SPFP: " will be directly forwarded to us
        self.outCollector.addResponsePrefix("SPFP: ")
        self.errCollector.addResponsePrefix("SPFP: ")
        
        self.outCollector.start()
        self.errCollector.start()
        
        try:
            self.process.stdin.write('selftest\n')
        except IOError:
            raise RuntimeError("SPFP Error: Selftest failed, application did not start properly.")

        try:
            response = self.responseQueue.get(block=True, timeout=self.processingTimeout)
        except Queue.Empty:
            raise RuntimeError("SPFP Error: Selftest failed, no response.")
        
        if response != "PASSED":
            raise RuntimeError("SPFP Error: Selftest failed, unsupported application response: %s" % response)
        
    def stop(self):
        self._terminateProcess()
                
        # Ensure we leave no dangling threads when stopping
        self.outCollector.join()
        self.errCollector.join()
        
        # Make the output available
        self.stdout = self.outCollector.output
        self.stderr = self.errCollector.output
        
    def runTest(self, test):
        if self.process == None or self.process.poll() != None:
            self.start()
        
        self.testLog.append(test)
        self.process.stdin.write('%s\n' % test)

        try:
            response = self.responseQueue.get(block=True, timeout=self.processingTimeout)
        except Queue.Empty:
            if self.process.poll() == None:
                # The process is still running, force it to stop and return timeout code
                self.stop()
                return ApplicationStatus.TIMEDOUT
            else:
                # The process has exited. We need to check if it crashed, but first we
                # call stop to join our collector threads.
                self.stop()
                
                if self.process.returncode < 0:
                    crashSignals = [
                                    # POSIX.1-1990 signals
                                    signal.SIGILL,
                                    signal.SIGABRT,
                                    signal.SIGFPE,
                                    signal.SIGSEGV,
                                    # SUSv2 / POSIX.1-2001 signals
                                    signal.SIGBUS,
                                    signal.SIGSYS,
                                    signal.SIGTRAP,
                                    ]
                    
                    for crashSignal in crashSignals:
                        if self.process.returncode == -crashSignal:
                            return ApplicationStatus.CRASHED
                    
                    # The application was terminated by a signal, but not by one of the listed signals.
                    # We consider this a fatal error. Either the signal should be supported here, or the
                    # process is being terminated by something else, making the testing unreliable.
                    #
                    # TODO: This could be triggered by the Linux kernel OOM killer
                    raise RuntimeError("SPFP Error: Application terminated with signal: %s" % self.process.returncode)
                else:
                    # The application exited, but didn't send us any message before doing so. We consider this
                    # a protocol violation and raise an exception.
                    raise RuntimeError("SPFP Error: Application exited without message. Exitcode: %s" % self.process.returncode)
        
        if response == 'OK':
            return ApplicationStatus.OK
        elif response == 'ERROR':
            return ApplicationStatus.ERROR
        
        raise RuntimeError("SPFP Error: Unsupported application response: %s" % response)
            
    def _terminateProcess(self):
        if self.process:
            if self.process.poll() == None:
                # Try to terminate the process gracefully first
                self.process.terminate()
                
                # Emulate a wait() with timeout. Because wait() having
                # a timeout would be way too easy, wouldn't it? -.-
                (maxSleepTime, pollInterval) = (3, 0.2)
                while self.process.poll() == None and maxSleepTime > 0:
                    maxSleepTime -= pollInterval
                    time.sleep(pollInterval)
                
                # Process is still alive, kill it and wait
                if self.process.poll() == None:
                    self.process.kill()
                    self.process.wait()