Beispiel #1
0
    def _setup_job_category(self):
        """
        This has to be called after self.logdir has been defined

        It attempts to create a directory one level up from the job results,
        with the given category name.  Then, a symbolic link is created to
        this job results directory.

        This should allow a user to look at a single directory for all
        jobs of a given category.
        """
        category = self.config.get("run.job_category")
        if category is None:
            return

        if category != astring.string_to_safe_path(category):
            msg = (
                f"Unable to set category in job results: name is not "
                f"filesystem safe: {category}"
            )
            LOG_UI.warning(msg)
            LOG_JOB.warning(msg)
            return

        # we could also get "base_logdir" from config, but I believe this is
        # the best choice because it reduces the dependency surface (depends
        # only on self.logdir)
        category_path = os.path.join(os.path.dirname(self.logdir), category)
        try:
            os.mkdir(category_path)
        except FileExistsError:
            pass

        try:
            os.symlink(
                os.path.relpath(self.logdir, category_path),
                os.path.join(category_path, os.path.basename(self.logdir)),
            )
        except NotImplementedError:
            msg = f"Unable to link this job to category {category}"
            LOG_UI.warning(msg)
            LOG_JOB.warning(msg)
        except OSError:
            msg = f"Permission denied to link this job to category {category}"
            LOG_UI.warning(msg)
            LOG_JOB.warning(msg)
Beispiel #2
0
    def finish(self, proc, started, step, deadline, result_dispatcher):
        """
        Wait for the test process to finish and report status or error status
        if unable to obtain the status till deadline.

        :param proc: The test's process
        :param started: Time when the test started
        :param first: Delay before first check
        :param step: Step between checks for the status
        :param deadline: Test execution deadline
        :param result_dispatcher: Result dispatcher (for test_progress
               notifications)
        """
        # Wait for either process termination or test status
        wait.wait_for(lambda: not proc.is_alive() or self.status, 1, 0, step)
        config = settings.as_dict()
        if self.status:  # status exists, wait for process to finish
            timeout_process_alive = config.get('runner.timeout.process_alive')
            deadline = min(deadline, time.monotonic() + timeout_process_alive)
            while time.monotonic() < deadline:
                result_dispatcher.map_method('test_progress', False)
                if wait.wait_for(lambda: not proc.is_alive(), 1, 0, step):
                    return self._add_status_failures(self.status)
            err = "Test reported status but did not finish"
        else:  # proc finished, wait for late status delivery
            timeout_process_died = config.get('runner.timeout.process_died')
            deadline = min(deadline, time.monotonic() + timeout_process_died)
            while time.monotonic() < deadline:
                result_dispatcher.map_method('test_progress', False)
                if wait.wait_for(lambda: self.status, 1, 0, step):
                    # Status delivered after the test process finished, pass
                    return self._add_status_failures(self.status)
            err = "Test died without reporting the status."
        # At this point there were failures, fill the new test status
        TEST_LOG.debug("Original status: %s", str(self.status))
        test_state = self.early_status
        test_state['time_start'] = started
        test_state['time_elapsed'] = time.monotonic() - started
        test_state['fail_reason'] = err
        test_state['status'] = exceptions.TestAbortError.status
        test_state['fail_class'] = (
            exceptions.TestAbortError.__class__.__name__)
        test_state['traceback'] = 'Traceback not available'
        try:
            with open(test_state['logfile'], 'r') as log_file_obj:
                test_state['text_output'] = log_file_obj.read()
        except IOError:
            test_state["text_output"] = "Not available, file not created yet"
        TEST_LOG.error('ERROR %s -> TestAbortError: %s.', err,
                       test_state['name'])
        if proc.is_alive():
            TEST_LOG.warning("Killing hanged test process %s", proc.pid)
            os.kill(proc.pid, signal.SIGTERM)
            if not wait.wait_for(lambda: not proc.is_alive(), 1, 0, 0.01):
                os.kill(proc.pid, signal.SIGKILL)
                end_time = time.monotonic() + 60
                while time.monotonic() < end_time:
                    if not proc.is_alive():
                        break
                    time.sleep(0.1)
                else:
                    raise exceptions.TestError("Unable to destroy test's "
                                               "process (%s)" % proc.pid)
        return self._add_status_failures(test_state)
Beispiel #3
0
 def soft_abort(msg):
     """ Only log the problem """
     LOG_JOB.warning("Unable to update the latest link: %s", msg)