test infra: move test event-related handling into its own package
This change moves all the test event handling and its related ResultsFormatter classes out of the packages/Python/lldbsuite/test dir into a packages/Python/lldbsuite/test_event package. Formatters are moved into a sub-package under that. I am limiting the scope of this change to just the motion and a few minor issues caught by a static Python checker (e.g. removing unused import statements). This is a pre-step for adding package-level tests to the test event system. I also intend to simplify test event results formatter selection after I make sure this doesn't break anybody. See: http://reviews.llvm.org/D19288 Reviewed by: Pavel Labath llvm-svn: 266885
This commit is contained in:
parent
e8fc69d136
commit
49d3c15c3e
|
@ -1,10 +1,9 @@
|
|||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
|
||||
# System modules
|
||||
from distutils.version import LooseVersion, StrictVersion
|
||||
from functools import wraps
|
||||
import itertools
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
@ -19,7 +18,7 @@ import use_lldb_suite
|
|||
import lldb
|
||||
from . import configuration
|
||||
from . import test_categories
|
||||
from .result_formatter import EventBuilder
|
||||
from lldbsuite.test_event.event_builder import EventBuilder
|
||||
from lldbsuite.support import funcutils
|
||||
from lldbsuite.test import lldbplatform
|
||||
from lldbsuite.test import lldbplatformutil
|
||||
|
@ -77,7 +76,6 @@ def expectedFailure(expected_fn, bugnumber=None):
|
|||
raise Exception("Decorator can only be used to decorate a test method")
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
from unittest2 import case
|
||||
self = args[0]
|
||||
if funcutils.requires_self(expected_fn):
|
||||
xfail_reason = expected_fn(self)
|
||||
|
@ -110,7 +108,6 @@ def skipTestIfFn(expected_fn, bugnumber=None):
|
|||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
from unittest2 import case
|
||||
self = args[0]
|
||||
if funcutils.requires_self(expected_fn):
|
||||
reason = expected_fn(self)
|
||||
|
|
|
@ -30,8 +30,8 @@ ulimit -c unlimited
|
|||
echo core.%p | sudo tee /proc/sys/kernel/core_pattern
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
|
||||
# system packages and modules
|
||||
import asyncore
|
||||
|
@ -52,13 +52,12 @@ from six.moves import queue
|
|||
import lldbsuite
|
||||
import lldbsuite.support.seven as seven
|
||||
|
||||
from lldbsuite.support import optional_with
|
||||
from . import configuration
|
||||
from . import dotest_channels
|
||||
from . import dotest_args
|
||||
from . import result_formatter
|
||||
|
||||
from .result_formatter import EventBuilder
|
||||
from lldbsuite.support import optional_with
|
||||
from lldbsuite.test_event import dotest_channels
|
||||
from lldbsuite.test_event.event_builder import EventBuilder
|
||||
from lldbsuite.test_event import formatter
|
||||
|
||||
from .test_runner import process_control
|
||||
|
||||
|
@ -299,9 +298,9 @@ def send_events_to_collector(events, command):
|
|||
event_port = int(command[arg_index])
|
||||
|
||||
# Create results formatter connected back to collector via socket.
|
||||
config = result_formatter.FormatterConfig()
|
||||
config = formatter.FormatterConfig()
|
||||
config.port = event_port
|
||||
formatter_spec = result_formatter.create_results_formatter(config)
|
||||
formatter_spec = formatter.create_results_formatter(config)
|
||||
if formatter_spec is None or formatter_spec.formatter is None:
|
||||
raise Exception(
|
||||
"Failed to create socket-based ResultsFormatter "
|
||||
|
|
|
@ -23,7 +23,6 @@ from __future__ import print_function
|
|||
|
||||
# System modules
|
||||
import atexit
|
||||
import importlib
|
||||
import os
|
||||
import errno
|
||||
import platform
|
||||
|
@ -31,7 +30,6 @@ import signal
|
|||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import inspect
|
||||
|
||||
# Third-party modules
|
||||
import six
|
||||
|
@ -43,9 +41,9 @@ from . import configuration
|
|||
from . import dotest_args
|
||||
from . import lldbtest_config
|
||||
from . import test_categories
|
||||
from . import result_formatter
|
||||
from lldbsuite.test_event import formatter
|
||||
from . import test_result
|
||||
from .result_formatter import EventBuilder
|
||||
from lldbsuite.test_event.event_builder import EventBuilder
|
||||
from ..support import seven
|
||||
|
||||
def is_exe(fpath):
|
||||
|
@ -359,7 +357,7 @@ def parseOptionsAndInitTestdirs():
|
|||
# Capture test results-related args.
|
||||
if args.curses and not args.inferior:
|
||||
# Act as if the following args were set.
|
||||
args.results_formatter = "lldbsuite.test.curses_results.Curses"
|
||||
args.results_formatter = "lldbsuite.test_event.formatter.curses.Curses"
|
||||
args.results_file = "stdout"
|
||||
|
||||
if args.results_file:
|
||||
|
@ -383,7 +381,7 @@ def parseOptionsAndInitTestdirs():
|
|||
# and we're not a test inferior.
|
||||
if not args.inferior and configuration.results_formatter_name is None:
|
||||
configuration.results_formatter_name = (
|
||||
"lldbsuite.test.result_formatter.ResultsFormatter")
|
||||
"lldbsuite.test_event.formatter.results_formatter.ResultsFormatter")
|
||||
|
||||
# rerun-related arguments
|
||||
configuration.rerun_all_issues = args.rerun_all_issues
|
||||
|
@ -412,7 +410,7 @@ def parseOptionsAndInitTestdirs():
|
|||
# Tell the event builder to create all events with these
|
||||
# key/val pairs in them.
|
||||
if len(entries) > 0:
|
||||
result_formatter.EventBuilder.add_entries_to_all_events(entries)
|
||||
EventBuilder.add_entries_to_all_events(entries)
|
||||
|
||||
# Gather all the dirs passed on the command line.
|
||||
if len(args.args) > 0:
|
||||
|
@ -453,7 +451,7 @@ def createSocketToLocalPort(port):
|
|||
def setupTestResults():
|
||||
"""Sets up test results-related objects based on arg settings."""
|
||||
# Setup the results formatter configuration.
|
||||
formatter_config = result_formatter.FormatterConfig()
|
||||
formatter_config = formatter.FormatterConfig()
|
||||
formatter_config.filename = configuration.results_filename
|
||||
formatter_config.formatter_name = configuration.results_formatter_name
|
||||
formatter_config.formatter_options = (
|
||||
|
@ -461,12 +459,12 @@ def setupTestResults():
|
|||
formatter_config.port = configuration.results_port
|
||||
|
||||
# Create the results formatter.
|
||||
formatter_spec = result_formatter.create_results_formatter(
|
||||
formatter_spec = formatter.create_results_formatter(
|
||||
formatter_config)
|
||||
if formatter_spec is not None and formatter_spec.formatter is not None:
|
||||
configuration.results_formatter_object = formatter_spec.formatter
|
||||
|
||||
# Send an intialize message to the formatter.
|
||||
# Send an initialize message to the formatter.
|
||||
initialize_event = EventBuilder.bare_event("initialize")
|
||||
if isMultiprocessTestRunner():
|
||||
if (configuration.test_runner_name is not None and
|
||||
|
|
|
@ -31,8 +31,8 @@ OK
|
|||
$
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
|
||||
# System modules
|
||||
import abc
|
||||
|
@ -42,12 +42,13 @@ import gc
|
|||
import glob
|
||||
import inspect
|
||||
import io
|
||||
import os, sys, traceback
|
||||
import os.path
|
||||
import re
|
||||
import signal
|
||||
from subprocess import *
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
import types
|
||||
|
||||
# Third-party modules
|
||||
|
@ -68,8 +69,6 @@ from . import test_categories
|
|||
from lldbsuite.support import encoded_file
|
||||
from lldbsuite.support import funcutils
|
||||
|
||||
from .result_formatter import EventBuilder
|
||||
|
||||
# dosep.py starts lots and lots of dotest instances
|
||||
# This option helps you find if two (or more) dotest instances are using the same
|
||||
# directory at the same time
|
||||
|
|
|
@ -18,9 +18,8 @@ import inspect
|
|||
import unittest2
|
||||
|
||||
# LLDB Modules
|
||||
import lldbsuite
|
||||
from . import configuration
|
||||
from .result_formatter import EventBuilder
|
||||
from lldbsuite.test_event.event_builder import EventBuilder
|
||||
|
||||
|
||||
class LLDBTestResult(unittest2.TextTestResult):
|
||||
|
|
|
@ -0,0 +1,435 @@
|
|||
"""
|
||||
The LLVM Compiler Infrastructure
|
||||
|
||||
This file is distributed under the University of Illinois Open Source
|
||||
License. See LICENSE.TXT for details.
|
||||
|
||||
Provides a class to build Python test event data structures.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
|
||||
# System modules
|
||||
import inspect
|
||||
import time
|
||||
import traceback
|
||||
|
||||
# Third-party modules
|
||||
|
||||
# LLDB modules
|
||||
|
||||
|
||||
class EventBuilder(object):
|
||||
"""Helper class to build test result event dictionaries."""
|
||||
|
||||
BASE_DICTIONARY = None
|
||||
|
||||
# Test Event Types
|
||||
TYPE_JOB_RESULT = "job_result"
|
||||
TYPE_TEST_RESULT = "test_result"
|
||||
TYPE_TEST_START = "test_start"
|
||||
TYPE_MARK_TEST_RERUN_ELIGIBLE = "test_eligible_for_rerun"
|
||||
TYPE_MARK_TEST_EXPECTED_FAILURE = "test_expected_failure"
|
||||
TYPE_SESSION_TERMINATE = "terminate"
|
||||
|
||||
RESULT_TYPES = {TYPE_JOB_RESULT, TYPE_TEST_RESULT}
|
||||
|
||||
# Test/Job Status Tags
|
||||
STATUS_EXCEPTIONAL_EXIT = "exceptional_exit"
|
||||
STATUS_SUCCESS = "success"
|
||||
STATUS_FAILURE = "failure"
|
||||
STATUS_EXPECTED_FAILURE = "expected_failure"
|
||||
STATUS_EXPECTED_TIMEOUT = "expected_timeout"
|
||||
STATUS_UNEXPECTED_SUCCESS = "unexpected_success"
|
||||
STATUS_SKIP = "skip"
|
||||
STATUS_ERROR = "error"
|
||||
STATUS_TIMEOUT = "timeout"
|
||||
|
||||
"""Test methods or jobs with a status matching any of these
|
||||
status values will cause a testrun failure, unless
|
||||
the test methods rerun and do not trigger an issue when rerun."""
|
||||
TESTRUN_ERROR_STATUS_VALUES = {STATUS_ERROR, STATUS_EXCEPTIONAL_EXIT, STATUS_FAILURE, STATUS_TIMEOUT}
|
||||
|
||||
@staticmethod
|
||||
def _get_test_name_info(test):
|
||||
"""Returns (test-class-name, test-method-name) from a test case instance.
|
||||
|
||||
@param test a unittest.TestCase instance.
|
||||
|
||||
@return tuple containing (test class name, test method name)
|
||||
"""
|
||||
test_class_components = test.id().split(".")
|
||||
test_class_name = ".".join(test_class_components[:-1])
|
||||
test_name = test_class_components[-1]
|
||||
return test_class_name, test_name
|
||||
|
||||
@staticmethod
|
||||
def bare_event(event_type):
|
||||
"""Creates an event with default additions, event type and timestamp.
|
||||
|
||||
@param event_type the value set for the "event" key, used
|
||||
to distinguish events.
|
||||
|
||||
@returns an event dictionary with all default additions, the "event"
|
||||
key set to the passed in event_type, and the event_time value set to
|
||||
time.time().
|
||||
"""
|
||||
if EventBuilder.BASE_DICTIONARY is not None:
|
||||
# Start with a copy of the "always include" entries.
|
||||
event = dict(EventBuilder.BASE_DICTIONARY)
|
||||
else:
|
||||
event = {}
|
||||
|
||||
event.update({
|
||||
"event": event_type,
|
||||
"event_time": time.time()
|
||||
})
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def _assert_is_python_sourcefile(test_filename):
|
||||
if test_filename is not None:
|
||||
if not test_filename.endswith(".py"):
|
||||
raise Exception("source python filename has unexpected extension: {}".format(test_filename))
|
||||
return test_filename
|
||||
|
||||
@staticmethod
|
||||
def _event_dictionary_common(test, event_type):
|
||||
"""Returns an event dictionary setup with values for the given event type.
|
||||
|
||||
@param test the unittest.TestCase instance
|
||||
|
||||
@param event_type the name of the event type (string).
|
||||
|
||||
@return event dictionary with common event fields set.
|
||||
"""
|
||||
test_class_name, test_name = EventBuilder._get_test_name_info(test)
|
||||
|
||||
# Determine the filename for the test case. If there is an attribute
|
||||
# for it, use it. Otherwise, determine from the TestCase class path.
|
||||
if hasattr(test, "test_filename"):
|
||||
test_filename = EventBuilder._assert_is_python_sourcefile(test.test_filename)
|
||||
else:
|
||||
test_filename = EventBuilder._assert_is_python_sourcefile(inspect.getsourcefile(test.__class__))
|
||||
|
||||
event = EventBuilder.bare_event(event_type)
|
||||
event.update({
|
||||
"test_class": test_class_name,
|
||||
"test_name": test_name,
|
||||
"test_filename": test_filename
|
||||
})
|
||||
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def _error_tuple_class(error_tuple):
|
||||
"""Returns the unittest error tuple's error class as a string.
|
||||
|
||||
@param error_tuple the error tuple provided by the test framework.
|
||||
|
||||
@return the error type (typically an exception) raised by the
|
||||
test framework.
|
||||
"""
|
||||
type_var = error_tuple[0]
|
||||
module = inspect.getmodule(type_var)
|
||||
if module:
|
||||
return "{}.{}".format(module.__name__, type_var.__name__)
|
||||
else:
|
||||
return type_var.__name__
|
||||
|
||||
@staticmethod
|
||||
def _error_tuple_message(error_tuple):
|
||||
"""Returns the unittest error tuple's error message.
|
||||
|
||||
@param error_tuple the error tuple provided by the test framework.
|
||||
|
||||
@return the error message provided by the test framework.
|
||||
"""
|
||||
return str(error_tuple[1])
|
||||
|
||||
@staticmethod
|
||||
def _error_tuple_traceback(error_tuple):
|
||||
"""Returns the unittest error tuple's error message.
|
||||
|
||||
@param error_tuple the error tuple provided by the test framework.
|
||||
|
||||
@return the error message provided by the test framework.
|
||||
"""
|
||||
return error_tuple[2]
|
||||
|
||||
@staticmethod
|
||||
def _event_dictionary_test_result(test, status):
|
||||
"""Returns an event dictionary with common test result fields set.
|
||||
|
||||
@param test a unittest.TestCase instance.
|
||||
|
||||
@param status the status/result of the test
|
||||
(e.g. "success", "failure", etc.)
|
||||
|
||||
@return the event dictionary
|
||||
"""
|
||||
event = EventBuilder._event_dictionary_common(
|
||||
test, EventBuilder.TYPE_TEST_RESULT)
|
||||
event["status"] = status
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def _event_dictionary_issue(test, status, error_tuple):
|
||||
"""Returns an event dictionary with common issue-containing test result
|
||||
fields set.
|
||||
|
||||
@param test a unittest.TestCase instance.
|
||||
|
||||
@param status the status/result of the test
|
||||
(e.g. "success", "failure", etc.)
|
||||
|
||||
@param error_tuple the error tuple as reported by the test runner.
|
||||
This is of the form (type<error>, error).
|
||||
|
||||
@return the event dictionary
|
||||
"""
|
||||
event = EventBuilder._event_dictionary_test_result(test, status)
|
||||
event["issue_class"] = EventBuilder._error_tuple_class(error_tuple)
|
||||
event["issue_message"] = EventBuilder._error_tuple_message(error_tuple)
|
||||
backtrace = EventBuilder._error_tuple_traceback(error_tuple)
|
||||
if backtrace is not None:
|
||||
event["issue_backtrace"] = traceback.format_tb(backtrace)
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def event_for_start(test):
|
||||
"""Returns an event dictionary for the test start event.
|
||||
|
||||
@param test a unittest.TestCase instance.
|
||||
|
||||
@return the event dictionary
|
||||
"""
|
||||
return EventBuilder._event_dictionary_common(
|
||||
test, EventBuilder.TYPE_TEST_START)
|
||||
|
||||
@staticmethod
|
||||
def event_for_success(test):
|
||||
"""Returns an event dictionary for a successful test.
|
||||
|
||||
@param test a unittest.TestCase instance.
|
||||
|
||||
@return the event dictionary
|
||||
"""
|
||||
return EventBuilder._event_dictionary_test_result(
|
||||
test, EventBuilder.STATUS_SUCCESS)
|
||||
|
||||
@staticmethod
|
||||
def event_for_unexpected_success(test, bugnumber):
|
||||
"""Returns an event dictionary for a test that succeeded but was
|
||||
expected to fail.
|
||||
|
||||
@param test a unittest.TestCase instance.
|
||||
|
||||
@param bugnumber the issue identifier for the bug tracking the
|
||||
fix request for the test expected to fail (but is in fact
|
||||
passing here).
|
||||
|
||||
@return the event dictionary
|
||||
|
||||
"""
|
||||
event = EventBuilder._event_dictionary_test_result(
|
||||
test, EventBuilder.STATUS_UNEXPECTED_SUCCESS)
|
||||
if bugnumber:
|
||||
event["bugnumber"] = str(bugnumber)
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def event_for_failure(test, error_tuple):
|
||||
"""Returns an event dictionary for a test that failed.
|
||||
|
||||
@param test a unittest.TestCase instance.
|
||||
|
||||
@param error_tuple the error tuple as reported by the test runner.
|
||||
This is of the form (type<error>, error).
|
||||
|
||||
@return the event dictionary
|
||||
"""
|
||||
return EventBuilder._event_dictionary_issue(
|
||||
test, EventBuilder.STATUS_FAILURE, error_tuple)
|
||||
|
||||
@staticmethod
|
||||
def event_for_expected_failure(test, error_tuple, bugnumber):
|
||||
"""Returns an event dictionary for a test that failed as expected.
|
||||
|
||||
@param test a unittest.TestCase instance.
|
||||
|
||||
@param error_tuple the error tuple as reported by the test runner.
|
||||
This is of the form (type<error>, error).
|
||||
|
||||
@param bugnumber the issue identifier for the bug tracking the
|
||||
fix request for the test expected to fail.
|
||||
|
||||
@return the event dictionary
|
||||
|
||||
"""
|
||||
event = EventBuilder._event_dictionary_issue(
|
||||
test, EventBuilder.STATUS_EXPECTED_FAILURE, error_tuple)
|
||||
if bugnumber:
|
||||
event["bugnumber"] = str(bugnumber)
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def event_for_skip(test, reason):
|
||||
"""Returns an event dictionary for a test that was skipped.
|
||||
|
||||
@param test a unittest.TestCase instance.
|
||||
|
||||
@param reason the reason why the test is being skipped.
|
||||
|
||||
@return the event dictionary
|
||||
"""
|
||||
event = EventBuilder._event_dictionary_test_result(
|
||||
test, EventBuilder.STATUS_SKIP)
|
||||
event["skip_reason"] = reason
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def event_for_error(test, error_tuple):
|
||||
"""Returns an event dictionary for a test that hit a test execution error.
|
||||
|
||||
@param test a unittest.TestCase instance.
|
||||
|
||||
@param error_tuple the error tuple as reported by the test runner.
|
||||
This is of the form (type<error>, error).
|
||||
|
||||
@return the event dictionary
|
||||
"""
|
||||
return EventBuilder._event_dictionary_issue(
|
||||
test, EventBuilder.STATUS_ERROR, error_tuple)
|
||||
|
||||
@staticmethod
|
||||
def event_for_cleanup_error(test, error_tuple):
|
||||
"""Returns an event dictionary for a test that hit a test execution error
|
||||
during the test cleanup phase.
|
||||
|
||||
@param test a unittest.TestCase instance.
|
||||
|
||||
@param error_tuple the error tuple as reported by the test runner.
|
||||
This is of the form (type<error>, error).
|
||||
|
||||
@return the event dictionary
|
||||
"""
|
||||
event = EventBuilder._event_dictionary_issue(
|
||||
test, EventBuilder.STATUS_ERROR, error_tuple)
|
||||
event["issue_phase"] = "cleanup"
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def event_for_job_exceptional_exit(
|
||||
pid, worker_index, exception_code, exception_description,
|
||||
test_filename, command_line):
|
||||
"""Creates an event for a job (i.e. process) exit due to signal.
|
||||
|
||||
@param pid the process id for the job that failed
|
||||
@param worker_index optional id for the job queue running the process
|
||||
@param exception_code optional code
|
||||
(e.g. SIGTERM integer signal number)
|
||||
@param exception_description optional string containing symbolic
|
||||
representation of the issue (e.g. "SIGTERM")
|
||||
@param test_filename the path to the test filename that exited
|
||||
in some exceptional way.
|
||||
@param command_line the Popen()-style list provided as the command line
|
||||
for the process that timed out.
|
||||
|
||||
@return an event dictionary coding the job completion description.
|
||||
"""
|
||||
event = EventBuilder.bare_event(EventBuilder.TYPE_JOB_RESULT)
|
||||
event["status"] = EventBuilder.STATUS_EXCEPTIONAL_EXIT
|
||||
if pid is not None:
|
||||
event["pid"] = pid
|
||||
if worker_index is not None:
|
||||
event["worker_index"] = int(worker_index)
|
||||
if exception_code is not None:
|
||||
event["exception_code"] = exception_code
|
||||
if exception_description is not None:
|
||||
event["exception_description"] = exception_description
|
||||
if test_filename is not None:
|
||||
event["test_filename"] = EventBuilder._assert_is_python_sourcefile(test_filename)
|
||||
if command_line is not None:
|
||||
event["command_line"] = command_line
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def event_for_job_timeout(pid, worker_index, test_filename, command_line):
|
||||
"""Creates an event for a job (i.e. process) timeout.
|
||||
|
||||
@param pid the process id for the job that timed out
|
||||
@param worker_index optional id for the job queue running the process
|
||||
@param test_filename the path to the test filename that timed out.
|
||||
@param command_line the Popen-style list provided as the command line
|
||||
for the process that timed out.
|
||||
|
||||
@return an event dictionary coding the job completion description.
|
||||
"""
|
||||
event = EventBuilder.bare_event(EventBuilder.TYPE_JOB_RESULT)
|
||||
event["status"] = "timeout"
|
||||
if pid is not None:
|
||||
event["pid"] = pid
|
||||
if worker_index is not None:
|
||||
event["worker_index"] = int(worker_index)
|
||||
if test_filename is not None:
|
||||
event["test_filename"] = EventBuilder._assert_is_python_sourcefile(test_filename)
|
||||
if command_line is not None:
|
||||
event["command_line"] = command_line
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def event_for_mark_test_rerun_eligible(test):
|
||||
"""Creates an event that indicates the specified test is explicitly
|
||||
eligible for rerun.
|
||||
|
||||
Note there is a mode that will enable test rerun eligibility at the
|
||||
global level. These markings for explicit rerun eligibility are
|
||||
intended for the mode of running where only explicitly re-runnable
|
||||
tests are rerun upon hitting an issue.
|
||||
|
||||
@param test the TestCase instance to which this pertains.
|
||||
|
||||
@return an event that specifies the given test as being eligible to
|
||||
be rerun.
|
||||
"""
|
||||
event = EventBuilder._event_dictionary_common(
|
||||
test,
|
||||
EventBuilder.TYPE_MARK_TEST_RERUN_ELIGIBLE)
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def event_for_mark_test_expected_failure(test):
|
||||
"""Creates an event that indicates the specified test is expected
|
||||
to fail.
|
||||
|
||||
@param test the TestCase instance to which this pertains.
|
||||
|
||||
@return an event that specifies the given test is expected to fail.
|
||||
"""
|
||||
event = EventBuilder._event_dictionary_common(
|
||||
test,
|
||||
EventBuilder.TYPE_MARK_TEST_EXPECTED_FAILURE)
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def add_entries_to_all_events(entries_dict):
|
||||
"""Specifies a dictionary of entries to add to all test events.
|
||||
|
||||
This provides a mechanism for, say, a parallel test runner to
|
||||
indicate to each inferior dotest.py that it should add a
|
||||
worker index to each.
|
||||
|
||||
Calling this method replaces all previous entries added
|
||||
by a prior call to this.
|
||||
|
||||
Event build methods will overwrite any entries that collide.
|
||||
Thus, the passed in dictionary is the base, which gets merged
|
||||
over by event building when keys collide.
|
||||
|
||||
@param entries_dict a dictionary containing key and value
|
||||
pairs that should be merged into all events created by the
|
||||
event generator. May be None to clear out any extra entries.
|
||||
"""
|
||||
EventBuilder.BASE_DICTIONARY = dict(entries_dict)
|
|
@ -0,0 +1,160 @@
|
|||
"""
|
||||
The LLVM Compiler Infrastructure
|
||||
|
||||
This file is distributed under the University of Illinois Open Source
|
||||
License. See LICENSE.TXT for details.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
|
||||
# System modules
|
||||
import importlib
|
||||
import socket
|
||||
import sys
|
||||
|
||||
# Third-party modules
|
||||
|
||||
# LLDB modules
|
||||
|
||||
|
||||
# Ignore method count on DTOs.
|
||||
# pylint: disable=too-few-public-methods
|
||||
class FormatterConfig(object):
|
||||
"""Provides formatter configuration info to create_results_formatter()."""
|
||||
|
||||
def __init__(self):
|
||||
self.filename = None
|
||||
self.port = None
|
||||
self.formatter_name = None
|
||||
self.formatter_options = None
|
||||
|
||||
|
||||
# Ignore method count on DTOs.
|
||||
# pylint: disable=too-few-public-methods
|
||||
class CreatedFormatter(object):
|
||||
"""Provides transfer object for returns from create_results_formatter()."""
|
||||
|
||||
def __init__(self, formatter, cleanup_func):
|
||||
self.formatter = formatter
|
||||
self.cleanup_func = cleanup_func
|
||||
|
||||
|
||||
SOCKET_ACK_BYTE_VALUE = b'*' # ASCII for chr(42)
|
||||
|
||||
|
||||
def create_results_formatter(config):
|
||||
"""Sets up a test results formatter.
|
||||
|
||||
@param config an instance of FormatterConfig
|
||||
that indicates how to setup the ResultsFormatter.
|
||||
|
||||
@return an instance of CreatedFormatter.
|
||||
"""
|
||||
|
||||
def create_socket(port):
|
||||
"""Creates a socket to the localhost on the given port.
|
||||
|
||||
@param port the port number of the listening port on
|
||||
the localhost.
|
||||
|
||||
@return (socket object, socket closing function)
|
||||
"""
|
||||
|
||||
def socket_closer(open_sock):
|
||||
"""Close down an opened socket properly."""
|
||||
open_sock.shutdown(socket.SHUT_RDWR)
|
||||
open_sock.close()
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("localhost", port))
|
||||
|
||||
# Wait for the ack from the listener side.
|
||||
# This is needed to prevent a race condition
|
||||
# in the main dosep.py processing loop: we
|
||||
# can't allow a worker queue thread to die
|
||||
# that has outstanding messages to a listener
|
||||
# socket before the listener socket asyncore
|
||||
# listener socket gets spun up; otherwise,
|
||||
# we lose the test result info.
|
||||
read_bytes = sock.recv(1)
|
||||
if read_bytes is None or (len(read_bytes) < 1) or (read_bytes[0] != SOCKET_ACK_BYTE_VALUE):
|
||||
raise Exception("listening socket did not respond with ack byte: response={}".format(read_bytes))
|
||||
|
||||
return sock, lambda: socket_closer(sock)
|
||||
|
||||
default_formatter_name = None
|
||||
results_file_object = None
|
||||
cleanup_func = None
|
||||
|
||||
if config.filename:
|
||||
# Open the results file for writing.
|
||||
if config.filename == 'stdout':
|
||||
results_file_object = sys.stdout
|
||||
cleanup_func = None
|
||||
elif config.filename == 'stderr':
|
||||
results_file_object = sys.stderr
|
||||
cleanup_func = None
|
||||
else:
|
||||
results_file_object = open(config.filename, "w")
|
||||
cleanup_func = results_file_object.close
|
||||
default_formatter_name = (
|
||||
"lldbsuite.test_event.formatter.xunit.XunitFormatter")
|
||||
elif config.port:
|
||||
# Connect to the specified localhost port.
|
||||
results_file_object, cleanup_func = create_socket(config.port)
|
||||
default_formatter_name = (
|
||||
"lldbsuite.test_event.formatter.pickled.RawPickledFormatter")
|
||||
|
||||
# If we have a results formatter name specified and we didn't specify
|
||||
# a results file, we should use stdout.
|
||||
if config.formatter_name is not None and results_file_object is None:
|
||||
# Use stdout.
|
||||
results_file_object = sys.stdout
|
||||
cleanup_func = None
|
||||
|
||||
if results_file_object:
|
||||
# We care about the formatter. Choose user-specified or, if
|
||||
# none specified, use the default for the output type.
|
||||
if config.formatter_name:
|
||||
formatter_name = config.formatter_name
|
||||
else:
|
||||
formatter_name = default_formatter_name
|
||||
|
||||
# Create an instance of the class.
|
||||
# First figure out the package/module.
|
||||
components = formatter_name.split(".")
|
||||
module = importlib.import_module(".".join(components[:-1]))
|
||||
|
||||
# Create the class name we need to load.
|
||||
cls = getattr(module, components[-1])
|
||||
|
||||
# Handle formatter options for the results formatter class.
|
||||
formatter_arg_parser = cls.arg_parser()
|
||||
if config.formatter_options and len(config.formatter_options) > 0:
|
||||
command_line_options = config.formatter_options
|
||||
else:
|
||||
command_line_options = []
|
||||
|
||||
formatter_options = formatter_arg_parser.parse_args(
|
||||
command_line_options)
|
||||
|
||||
# Create the TestResultsFormatter given the processed options.
|
||||
results_formatter_object = cls(results_file_object, formatter_options)
|
||||
|
||||
def shutdown_formatter():
|
||||
"""Shuts down the formatter when it is no longer needed."""
|
||||
# Tell the formatter to write out anything it may have
|
||||
# been saving until the very end (e.g. xUnit results
|
||||
# can't complete its output until this point).
|
||||
results_formatter_object.send_terminate_as_needed()
|
||||
|
||||
# And now close out the output file-like object.
|
||||
if cleanup_func is not None:
|
||||
cleanup_func()
|
||||
|
||||
return CreatedFormatter(
|
||||
results_formatter_object,
|
||||
shutdown_formatter)
|
||||
else:
|
||||
return None
|
|
@ -1,16 +1,12 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
The LLVM Compiler Infrastructure
|
||||
|
||||
This file is distributed under the University of Illinois Open Source
|
||||
License. See LICENSE.TXT for details.
|
||||
|
||||
Configuration options for lldbtest.py set by dotest.py during initialization
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
|
||||
# System modules
|
||||
import curses
|
||||
|
@ -22,12 +18,13 @@ import time
|
|||
# Third-party modules
|
||||
|
||||
# LLDB modules
|
||||
from . import lldbcurses
|
||||
from . import result_formatter
|
||||
from .result_formatter import EventBuilder
|
||||
from lldbsuite.test import lldbcurses
|
||||
|
||||
from . import results_formatter
|
||||
from ..event_builder import EventBuilder
|
||||
|
||||
|
||||
class Curses(result_formatter.ResultsFormatter):
|
||||
class Curses(results_formatter.ResultsFormatter):
|
||||
"""Receives live results from tests that are running and reports them to the terminal in a curses GUI"""
|
||||
|
||||
def __init__(self, out_file, options):
|
||||
|
@ -75,6 +72,10 @@ class Curses(result_formatter.ResultsFormatter):
|
|||
return 'S'
|
||||
elif status == EventBuilder.STATUS_ERROR:
|
||||
return 'E'
|
||||
elif status == EventBuilder.STATUS_TIMEOUT:
|
||||
return 'T'
|
||||
elif status == EventBuilder.STATUS_EXPECTED_TIMEOUT:
|
||||
return 't'
|
||||
else:
|
||||
return status
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
"""
|
||||
The LLVM Compiler Infrastructure
|
||||
|
||||
This file is distributed under the University of Illinois Open Source
|
||||
License. See LICENSE.TXT for details.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
|
||||
# System modules
|
||||
import pprint
|
||||
|
||||
# Our modules
|
||||
from .results_formatter import ResultsFormatter
|
||||
|
||||
|
||||
class DumpFormatter(ResultsFormatter):
|
||||
"""Formats events to the file as their raw python dictionary format."""
|
||||
|
||||
def handle_event(self, test_event):
|
||||
super(DumpFormatter, self).handle_event(test_event)
|
||||
self.out_file.write("\n" + pprint.pformat(test_event) + "\n")
|
|
@ -0,0 +1,57 @@
|
|||
"""
|
||||
The LLVM Compiler Infrastructure
|
||||
|
||||
This file is distributed under the University of Illinois Open Source
|
||||
License. See LICENSE.TXT for details.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
|
||||
# System modules
|
||||
import os
|
||||
|
||||
# Our modules
|
||||
from .results_formatter import ResultsFormatter
|
||||
from six.moves import cPickle
|
||||
|
||||
|
||||
class RawPickledFormatter(ResultsFormatter):
|
||||
"""Formats events as a pickled stream.
|
||||
|
||||
The parallel test runner has inferiors pickle their results and send them
|
||||
over a socket back to the parallel test. The parallel test runner then
|
||||
aggregates them into the final results formatter (e.g. xUnit).
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def arg_parser(cls):
|
||||
"""@return arg parser used to parse formatter-specific options."""
|
||||
parser = super(RawPickledFormatter, cls).arg_parser()
|
||||
return parser
|
||||
|
||||
def __init__(self, out_file, options):
|
||||
super(RawPickledFormatter, self).__init__(out_file, options)
|
||||
self.pid = os.getpid()
|
||||
|
||||
def handle_event(self, test_event):
|
||||
super(RawPickledFormatter, self).handle_event(test_event)
|
||||
|
||||
# Convert initialize/terminate events into job_begin/job_end events.
|
||||
event_type = test_event["event"]
|
||||
if event_type is None:
|
||||
return
|
||||
|
||||
if event_type == "initialize":
|
||||
test_event["event"] = "job_begin"
|
||||
elif event_type == "terminate":
|
||||
test_event["event"] = "job_end"
|
||||
|
||||
# Tack on the pid.
|
||||
test_event["pid"] = self.pid
|
||||
|
||||
# Send it as {serialized_length_of_serialized_bytes}{serialized_bytes}
|
||||
import struct
|
||||
msg = cPickle.dumps(test_event)
|
||||
packet = struct.pack("!I%ds" % len(msg), len(msg), msg)
|
||||
self.out_file.send(packet)
|
|
@ -13,587 +13,24 @@ from __future__ import absolute_import
|
|||
|
||||
# System modules
|
||||
import argparse
|
||||
import importlib
|
||||
import inspect
|
||||
import os
|
||||
import pprint
|
||||
import socket
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
|
||||
# Third-party modules
|
||||
import six
|
||||
from six.moves import cPickle
|
||||
|
||||
|
||||
# LLDB modules
|
||||
from . import configuration
|
||||
from lldbsuite.test import configuration
|
||||
from ..event_builder import EventBuilder
|
||||
|
||||
import lldbsuite
|
||||
|
||||
|
||||
# Ignore method count on DTOs.
|
||||
# pylint: disable=too-few-public-methods
|
||||
class FormatterConfig(object):
|
||||
"""Provides formatter configuration info to create_results_formatter()."""
|
||||
def __init__(self):
|
||||
self.filename = None
|
||||
self.port = None
|
||||
self.formatter_name = None
|
||||
self.formatter_options = None
|
||||
|
||||
|
||||
# Ignore method count on DTOs.
|
||||
# pylint: disable=too-few-public-methods
|
||||
class CreatedFormatter(object):
|
||||
"""Provides transfer object for returns from create_results_formatter()."""
|
||||
def __init__(self, formatter, cleanup_func):
|
||||
self.formatter = formatter
|
||||
self.cleanup_func = cleanup_func
|
||||
|
||||
|
||||
def create_results_formatter(config):
|
||||
"""Sets up a test results formatter.
|
||||
|
||||
@param config an instance of FormatterConfig
|
||||
that indicates how to setup the ResultsFormatter.
|
||||
|
||||
@return an instance of CreatedFormatter.
|
||||
"""
|
||||
def create_socket(port):
|
||||
"""Creates a socket to the localhost on the given port.
|
||||
|
||||
@param port the port number of the listening port on
|
||||
the localhost.
|
||||
|
||||
@return (socket object, socket closing function)
|
||||
"""
|
||||
def socket_closer(open_sock):
|
||||
"""Close down an opened socket properly."""
|
||||
open_sock.shutdown(socket.SHUT_RDWR)
|
||||
open_sock.close()
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("localhost", port))
|
||||
|
||||
# Wait for the ack from the listener side.
|
||||
# This is needed to prevent a race condition
|
||||
# in the main dosep.py processing loop: we
|
||||
# can't allow a worker queue thread to die
|
||||
# that has outstanding messages to a listener
|
||||
# socket before the listener socket asyncore
|
||||
# listener socket gets spun up; otherwise,
|
||||
# we lose the test result info.
|
||||
read_bytes = sock.recv(1)
|
||||
# print("\n** socket creation: received ack: {}".format(ord(read_bytes[0])), file=sys.stderr)
|
||||
|
||||
return (sock, lambda: socket_closer(sock))
|
||||
|
||||
default_formatter_name = None
|
||||
results_file_object = None
|
||||
cleanup_func = None
|
||||
|
||||
if config.filename:
|
||||
# Open the results file for writing.
|
||||
if config.filename == 'stdout':
|
||||
results_file_object = sys.stdout
|
||||
cleanup_func = None
|
||||
elif config.filename == 'stderr':
|
||||
results_file_object = sys.stderr
|
||||
cleanup_func = None
|
||||
else:
|
||||
results_file_object = open(config.filename, "w")
|
||||
cleanup_func = results_file_object.close
|
||||
default_formatter_name = (
|
||||
"lldbsuite.test.xunit_formatter.XunitFormatter")
|
||||
elif config.port:
|
||||
# Connect to the specified localhost port.
|
||||
results_file_object, cleanup_func = create_socket(config.port)
|
||||
default_formatter_name = (
|
||||
"lldbsuite.test.result_formatter.RawPickledFormatter")
|
||||
|
||||
# If we have a results formatter name specified and we didn't specify
|
||||
# a results file, we should use stdout.
|
||||
if config.formatter_name is not None and results_file_object is None:
|
||||
# Use stdout.
|
||||
results_file_object = sys.stdout
|
||||
cleanup_func = None
|
||||
|
||||
if results_file_object:
|
||||
# We care about the formatter. Choose user-specified or, if
|
||||
# none specified, use the default for the output type.
|
||||
if config.formatter_name:
|
||||
formatter_name = config.formatter_name
|
||||
else:
|
||||
formatter_name = default_formatter_name
|
||||
|
||||
# Create an instance of the class.
|
||||
# First figure out the package/module.
|
||||
components = formatter_name.split(".")
|
||||
module = importlib.import_module(".".join(components[:-1]))
|
||||
|
||||
# Create the class name we need to load.
|
||||
cls = getattr(module, components[-1])
|
||||
|
||||
# Handle formatter options for the results formatter class.
|
||||
formatter_arg_parser = cls.arg_parser()
|
||||
if config.formatter_options and len(config.formatter_options) > 0:
|
||||
command_line_options = config.formatter_options
|
||||
else:
|
||||
command_line_options = []
|
||||
|
||||
formatter_options = formatter_arg_parser.parse_args(
|
||||
command_line_options)
|
||||
|
||||
# Create the TestResultsFormatter given the processed options.
|
||||
results_formatter_object = cls(results_file_object, formatter_options)
|
||||
|
||||
def shutdown_formatter():
|
||||
"""Shuts down the formatter when it is no longer needed."""
|
||||
# Tell the formatter to write out anything it may have
|
||||
# been saving until the very end (e.g. xUnit results
|
||||
# can't complete its output until this point).
|
||||
results_formatter_object.send_terminate_as_needed()
|
||||
|
||||
# And now close out the output file-like object.
|
||||
if cleanup_func is not None:
|
||||
cleanup_func()
|
||||
|
||||
return CreatedFormatter(
|
||||
results_formatter_object,
|
||||
shutdown_formatter)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class EventBuilder(object):
|
||||
"""Helper class to build test result event dictionaries."""
|
||||
|
||||
BASE_DICTIONARY = None
|
||||
|
||||
# Test Event Types
|
||||
TYPE_JOB_RESULT = "job_result"
|
||||
TYPE_TEST_RESULT = "test_result"
|
||||
TYPE_TEST_START = "test_start"
|
||||
TYPE_MARK_TEST_RERUN_ELIGIBLE = "test_eligible_for_rerun"
|
||||
TYPE_MARK_TEST_EXPECTED_FAILURE = "test_expected_failure"
|
||||
TYPE_SESSION_TERMINATE = "terminate"
|
||||
|
||||
RESULT_TYPES = set([
|
||||
TYPE_JOB_RESULT,
|
||||
TYPE_TEST_RESULT
|
||||
])
|
||||
|
||||
# Test/Job Status Tags
|
||||
STATUS_EXCEPTIONAL_EXIT = "exceptional_exit"
|
||||
STATUS_SUCCESS = "success"
|
||||
STATUS_FAILURE = "failure"
|
||||
STATUS_EXPECTED_FAILURE = "expected_failure"
|
||||
STATUS_EXPECTED_TIMEOUT = "expected_timeout"
|
||||
STATUS_UNEXPECTED_SUCCESS = "unexpected_success"
|
||||
STATUS_SKIP = "skip"
|
||||
STATUS_ERROR = "error"
|
||||
STATUS_TIMEOUT = "timeout"
|
||||
|
||||
"""Test methods or jobs with a status matching any of these
|
||||
status values will cause a testrun failure, unless
|
||||
the test methods rerun and do not trigger an issue when rerun."""
|
||||
TESTRUN_ERROR_STATUS_VALUES = set([
|
||||
STATUS_ERROR,
|
||||
STATUS_EXCEPTIONAL_EXIT,
|
||||
STATUS_FAILURE,
|
||||
STATUS_TIMEOUT
|
||||
])
|
||||
|
||||
@staticmethod
|
||||
def _get_test_name_info(test):
|
||||
"""Returns (test-class-name, test-method-name) from a test case instance.
|
||||
|
||||
@param test a unittest.TestCase instance.
|
||||
|
||||
@return tuple containing (test class name, test method name)
|
||||
"""
|
||||
test_class_components = test.id().split(".")
|
||||
test_class_name = ".".join(test_class_components[:-1])
|
||||
test_name = test_class_components[-1]
|
||||
return (test_class_name, test_name)
|
||||
|
||||
@staticmethod
|
||||
def bare_event(event_type):
|
||||
"""Creates an event with default additions, event type and timestamp.
|
||||
|
||||
@param event_type the value set for the "event" key, used
|
||||
to distinguish events.
|
||||
|
||||
@returns an event dictionary with all default additions, the "event"
|
||||
key set to the passed in event_type, and the event_time value set to
|
||||
time.time().
|
||||
"""
|
||||
if EventBuilder.BASE_DICTIONARY is not None:
|
||||
# Start with a copy of the "always include" entries.
|
||||
event = dict(EventBuilder.BASE_DICTIONARY)
|
||||
else:
|
||||
event = {}
|
||||
|
||||
event.update({
|
||||
"event": event_type,
|
||||
"event_time": time.time()
|
||||
})
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def _assert_is_python_sourcefile(test_filename):
|
||||
if test_filename is not None:
|
||||
if not test_filename.endswith(".py"):
|
||||
raise Exception("source python filename has unexpected extension: {}".format(test_filename))
|
||||
return test_filename
|
||||
|
||||
@staticmethod
|
||||
def _event_dictionary_common(test, event_type):
|
||||
"""Returns an event dictionary setup with values for the given event type.
|
||||
|
||||
@param test the unittest.TestCase instance
|
||||
|
||||
@param event_type the name of the event type (string).
|
||||
|
||||
@return event dictionary with common event fields set.
|
||||
"""
|
||||
test_class_name, test_name = EventBuilder._get_test_name_info(test)
|
||||
|
||||
# Determine the filename for the test case. If there is an attribute
|
||||
# for it, use it. Otherwise, determine from the TestCase class path.
|
||||
if hasattr(test, "test_filename"):
|
||||
test_filename = EventBuilder._assert_is_python_sourcefile(test.test_filename)
|
||||
else:
|
||||
test_filename = EventBuilder._assert_is_python_sourcefile(inspect.getsourcefile(test.__class__))
|
||||
|
||||
event = EventBuilder.bare_event(event_type)
|
||||
event.update({
|
||||
"test_class": test_class_name,
|
||||
"test_name": test_name,
|
||||
"test_filename": test_filename
|
||||
})
|
||||
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def _error_tuple_class(error_tuple):
|
||||
"""Returns the unittest error tuple's error class as a string.
|
||||
|
||||
@param error_tuple the error tuple provided by the test framework.
|
||||
|
||||
@return the error type (typically an exception) raised by the
|
||||
test framework.
|
||||
"""
|
||||
type_var = error_tuple[0]
|
||||
module = inspect.getmodule(type_var)
|
||||
if module:
|
||||
return "{}.{}".format(module.__name__, type_var.__name__)
|
||||
else:
|
||||
return type_var.__name__
|
||||
|
||||
@staticmethod
|
||||
def _error_tuple_message(error_tuple):
|
||||
"""Returns the unittest error tuple's error message.
|
||||
|
||||
@param error_tuple the error tuple provided by the test framework.
|
||||
|
||||
@return the error message provided by the test framework.
|
||||
"""
|
||||
return str(error_tuple[1])
|
||||
|
||||
@staticmethod
|
||||
def _error_tuple_traceback(error_tuple):
|
||||
"""Returns the unittest error tuple's error message.
|
||||
|
||||
@param error_tuple the error tuple provided by the test framework.
|
||||
|
||||
@return the error message provided by the test framework.
|
||||
"""
|
||||
return error_tuple[2]
|
||||
|
||||
@staticmethod
|
||||
def _event_dictionary_test_result(test, status):
|
||||
"""Returns an event dictionary with common test result fields set.
|
||||
|
||||
@param test a unittest.TestCase instance.
|
||||
|
||||
@param status the status/result of the test
|
||||
(e.g. "success", "failure", etc.)
|
||||
|
||||
@return the event dictionary
|
||||
"""
|
||||
event = EventBuilder._event_dictionary_common(
|
||||
test, EventBuilder.TYPE_TEST_RESULT)
|
||||
event["status"] = status
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def _event_dictionary_issue(test, status, error_tuple):
|
||||
"""Returns an event dictionary with common issue-containing test result
|
||||
fields set.
|
||||
|
||||
@param test a unittest.TestCase instance.
|
||||
|
||||
@param status the status/result of the test
|
||||
(e.g. "success", "failure", etc.)
|
||||
|
||||
@param error_tuple the error tuple as reported by the test runner.
|
||||
This is of the form (type<error>, error).
|
||||
|
||||
@return the event dictionary
|
||||
"""
|
||||
event = EventBuilder._event_dictionary_test_result(test, status)
|
||||
event["issue_class"] = EventBuilder._error_tuple_class(error_tuple)
|
||||
event["issue_message"] = EventBuilder._error_tuple_message(error_tuple)
|
||||
backtrace = EventBuilder._error_tuple_traceback(error_tuple)
|
||||
if backtrace is not None:
|
||||
event["issue_backtrace"] = traceback.format_tb(backtrace)
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def event_for_start(test):
|
||||
"""Returns an event dictionary for the test start event.
|
||||
|
||||
@param test a unittest.TestCase instance.
|
||||
|
||||
@return the event dictionary
|
||||
"""
|
||||
return EventBuilder._event_dictionary_common(
|
||||
test, EventBuilder.TYPE_TEST_START)
|
||||
|
||||
@staticmethod
|
||||
def event_for_success(test):
|
||||
"""Returns an event dictionary for a successful test.
|
||||
|
||||
@param test a unittest.TestCase instance.
|
||||
|
||||
@return the event dictionary
|
||||
"""
|
||||
return EventBuilder._event_dictionary_test_result(
|
||||
test, EventBuilder.STATUS_SUCCESS)
|
||||
|
||||
@staticmethod
|
||||
def event_for_unexpected_success(test, bugnumber):
|
||||
"""Returns an event dictionary for a test that succeeded but was
|
||||
expected to fail.
|
||||
|
||||
@param test a unittest.TestCase instance.
|
||||
|
||||
@param bugnumber the issue identifier for the bug tracking the
|
||||
fix request for the test expected to fail (but is in fact
|
||||
passing here).
|
||||
|
||||
@return the event dictionary
|
||||
|
||||
"""
|
||||
event = EventBuilder._event_dictionary_test_result(
|
||||
test, EventBuilder.STATUS_UNEXPECTED_SUCCESS)
|
||||
if bugnumber:
|
||||
event["bugnumber"] = str(bugnumber)
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def event_for_failure(test, error_tuple):
|
||||
"""Returns an event dictionary for a test that failed.
|
||||
|
||||
@param test a unittest.TestCase instance.
|
||||
|
||||
@param error_tuple the error tuple as reported by the test runner.
|
||||
This is of the form (type<error>, error).
|
||||
|
||||
@return the event dictionary
|
||||
"""
|
||||
return EventBuilder._event_dictionary_issue(
|
||||
test, EventBuilder.STATUS_FAILURE, error_tuple)
|
||||
|
||||
@staticmethod
|
||||
def event_for_expected_failure(test, error_tuple, bugnumber):
|
||||
"""Returns an event dictionary for a test that failed as expected.
|
||||
|
||||
@param test a unittest.TestCase instance.
|
||||
|
||||
@param error_tuple the error tuple as reported by the test runner.
|
||||
This is of the form (type<error>, error).
|
||||
|
||||
@param bugnumber the issue identifier for the bug tracking the
|
||||
fix request for the test expected to fail.
|
||||
|
||||
@return the event dictionary
|
||||
|
||||
"""
|
||||
event = EventBuilder._event_dictionary_issue(
|
||||
test, EventBuilder.STATUS_EXPECTED_FAILURE, error_tuple)
|
||||
if bugnumber:
|
||||
event["bugnumber"] = str(bugnumber)
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def event_for_skip(test, reason):
|
||||
"""Returns an event dictionary for a test that was skipped.
|
||||
|
||||
@param test a unittest.TestCase instance.
|
||||
|
||||
@param reason the reason why the test is being skipped.
|
||||
|
||||
@return the event dictionary
|
||||
"""
|
||||
event = EventBuilder._event_dictionary_test_result(
|
||||
test, EventBuilder.STATUS_SKIP)
|
||||
event["skip_reason"] = reason
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def event_for_error(test, error_tuple):
|
||||
"""Returns an event dictionary for a test that hit a test execution error.
|
||||
|
||||
@param test a unittest.TestCase instance.
|
||||
|
||||
@param error_tuple the error tuple as reported by the test runner.
|
||||
This is of the form (type<error>, error).
|
||||
|
||||
@return the event dictionary
|
||||
"""
|
||||
return EventBuilder._event_dictionary_issue(
|
||||
test, EventBuilder.STATUS_ERROR, error_tuple)
|
||||
|
||||
@staticmethod
|
||||
def event_for_cleanup_error(test, error_tuple):
|
||||
"""Returns an event dictionary for a test that hit a test execution error
|
||||
during the test cleanup phase.
|
||||
|
||||
@param test a unittest.TestCase instance.
|
||||
|
||||
@param error_tuple the error tuple as reported by the test runner.
|
||||
This is of the form (type<error>, error).
|
||||
|
||||
@return the event dictionary
|
||||
"""
|
||||
event = EventBuilder._event_dictionary_issue(
|
||||
test, EventBuilder.STATUS_ERROR, error_tuple)
|
||||
event["issue_phase"] = "cleanup"
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def event_for_job_exceptional_exit(
|
||||
pid, worker_index, exception_code, exception_description,
|
||||
test_filename, command_line):
|
||||
"""Creates an event for a job (i.e. process) exit due to signal.
|
||||
|
||||
@param pid the process id for the job that failed
|
||||
@param worker_index optional id for the job queue running the process
|
||||
@param exception_code optional code
|
||||
(e.g. SIGTERM integer signal number)
|
||||
@param exception_description optional string containing symbolic
|
||||
representation of the issue (e.g. "SIGTERM")
|
||||
@param test_filename the path to the test filename that exited
|
||||
in some exceptional way.
|
||||
@param command_line the Popen-style list provided as the command line
|
||||
for the process that timed out.
|
||||
|
||||
@return an event dictionary coding the job completion description.
|
||||
"""
|
||||
event = EventBuilder.bare_event(EventBuilder.TYPE_JOB_RESULT)
|
||||
event["status"] = EventBuilder.STATUS_EXCEPTIONAL_EXIT
|
||||
if pid is not None:
|
||||
event["pid"] = pid
|
||||
if worker_index is not None:
|
||||
event["worker_index"] = int(worker_index)
|
||||
if exception_code is not None:
|
||||
event["exception_code"] = exception_code
|
||||
if exception_description is not None:
|
||||
event["exception_description"] = exception_description
|
||||
if test_filename is not None:
|
||||
event["test_filename"] = EventBuilder._assert_is_python_sourcefile(test_filename)
|
||||
if command_line is not None:
|
||||
event["command_line"] = command_line
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def event_for_job_timeout(pid, worker_index, test_filename, command_line):
|
||||
"""Creates an event for a job (i.e. process) timeout.
|
||||
|
||||
@param pid the process id for the job that timed out
|
||||
@param worker_index optional id for the job queue running the process
|
||||
@param test_filename the path to the test filename that timed out.
|
||||
@param command_line the Popen-style list provided as the command line
|
||||
for the process that timed out.
|
||||
|
||||
@return an event dictionary coding the job completion description.
|
||||
"""
|
||||
event = EventBuilder.bare_event(EventBuilder.TYPE_JOB_RESULT)
|
||||
event["status"] = "timeout"
|
||||
if pid is not None:
|
||||
event["pid"] = pid
|
||||
if worker_index is not None:
|
||||
event["worker_index"] = int(worker_index)
|
||||
if test_filename is not None:
|
||||
event["test_filename"] = EventBuilder._assert_is_python_sourcefile(test_filename)
|
||||
if command_line is not None:
|
||||
event["command_line"] = command_line
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def event_for_mark_test_rerun_eligible(test):
|
||||
"""Creates an event that indicates the specified test is explicitly
|
||||
eligible for rerun.
|
||||
|
||||
Note there is a mode that will enable test rerun eligibility at the
|
||||
global level. These markings for explicit rerun eligibility are
|
||||
intended for the mode of running where only explicitly rerunnable
|
||||
tests are rerun upon hitting an issue.
|
||||
|
||||
@param test the TestCase instance to which this pertains.
|
||||
|
||||
@return an event that specifies the given test as being eligible to
|
||||
be rerun.
|
||||
"""
|
||||
event = EventBuilder._event_dictionary_common(
|
||||
test,
|
||||
EventBuilder.TYPE_MARK_TEST_RERUN_ELIGIBLE)
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def event_for_mark_test_expected_failure(test):
|
||||
"""Creates an event that indicates the specified test is expected
|
||||
to fail.
|
||||
|
||||
@param test the TestCase instance to which this pertains.
|
||||
|
||||
@return an event that specifies the given test is expected to fail.
|
||||
"""
|
||||
event = EventBuilder._event_dictionary_common(
|
||||
test,
|
||||
EventBuilder.TYPE_MARK_TEST_EXPECTED_FAILURE)
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def add_entries_to_all_events(entries_dict):
|
||||
"""Specifies a dictionary of entries to add to all test events.
|
||||
|
||||
This provides a mechanism for, say, a parallel test runner to
|
||||
indicate to each inferior dotest.py that it should add a
|
||||
worker index to each.
|
||||
|
||||
Calling this method replaces all previous entries added
|
||||
by a prior call to this.
|
||||
|
||||
Event build methods will overwrite any entries that collide.
|
||||
Thus, the passed in dictionary is the base, which gets merged
|
||||
over by event building when keys collide.
|
||||
|
||||
@param entries_dict a dictionary containing key and value
|
||||
pairs that should be merged into all events created by the
|
||||
event generator. May be None to clear out any extra entries.
|
||||
"""
|
||||
EventBuilder.BASE_DICTIONARY = dict(entries_dict)
|
||||
|
||||
|
||||
class ResultsFormatter(object):
|
||||
"""Provides interface to formatting test results out to a file-like object.
|
||||
|
||||
This class allows the LLDB test framework's raw test-realted
|
||||
This class allows the LLDB test framework's raw test-related
|
||||
events to be processed and formatted in any manner desired.
|
||||
Test events are represented by python dictionaries, formatted
|
||||
as in the EventBuilder class above.
|
||||
|
@ -609,7 +46,7 @@ class ResultsFormatter(object):
|
|||
# passed to dotest.py via the "--results-formatter-options"
|
||||
# argument. See the help on that for syntactic requirements
|
||||
# on getting that parsed correctly.
|
||||
formatter = SomeResultFormatter(file_like_object, argpared_options_dict)
|
||||
formatter = SomeResultFormatter(file_like_object, argparse_options_dict)
|
||||
|
||||
# Single call to session start, before parsing any events.
|
||||
formatter.begin_session()
|
||||
|
@ -625,7 +62,7 @@ class ResultsFormatter(object):
|
|||
for event in zero_or_more_test_events():
|
||||
formatter.handle_event(event)
|
||||
|
||||
# Single call to terminate/wrap-up. Formatters that need all the
|
||||
# Single call to terminate/wrap-up. For formatters that need all the
|
||||
# data before they can print a correct result (e.g. xUnit/JUnit),
|
||||
# this is where the final report can be generated.
|
||||
formatter.handle_event({"event":"terminate",...})
|
||||
|
@ -755,6 +192,8 @@ class ResultsFormatter(object):
|
|||
if "test_filename" in result_event:
|
||||
key = result_event["test_filename"]
|
||||
component_count += 1
|
||||
else:
|
||||
key = "<no_filename>"
|
||||
if "test_class" in result_event:
|
||||
if component_count > 0:
|
||||
key += ":"
|
||||
|
@ -1010,6 +449,7 @@ class ResultsFormatter(object):
|
|||
|
||||
# Derived classes may require self access
|
||||
# pylint: disable=no-self-use
|
||||
# noinspection PyMethodMayBeStatic,PyMethodMayBeStatic
|
||||
def replaces_summary(self):
|
||||
"""Returns whether the results formatter includes a summary
|
||||
suitable to replace the old lldb test run results.
|
||||
|
@ -1070,7 +510,8 @@ class ResultsFormatter(object):
|
|||
key=lambda x: self._event_sort_key(x[1]))
|
||||
return partitioned_events
|
||||
|
||||
def _print_banner(self, out_file, banner_text):
|
||||
@staticmethod
|
||||
def _print_banner(out_file, banner_text):
|
||||
"""Prints an ASCII banner around given text.
|
||||
|
||||
Output goes to the out file for the results formatter.
|
||||
|
@ -1161,7 +602,8 @@ class ResultsFormatter(object):
|
|||
# details.
|
||||
return False
|
||||
|
||||
def _report_category_details(self, out_file, category, result_events_by_status):
|
||||
@staticmethod
|
||||
def _report_category_details(out_file, category, result_events_by_status):
|
||||
"""Reports all test results matching the given category spec.
|
||||
|
||||
@param out_file a file-like object used to print output.
|
||||
|
@ -1266,58 +708,9 @@ class ResultsFormatter(object):
|
|||
|
||||
if self.options.dump_results:
|
||||
# Debug dump of the key/result info for all categories.
|
||||
self._print_banner("Results Dump")
|
||||
self._print_banner(out_file, "Results Dump")
|
||||
for status, events_by_key in result_events_by_status.items():
|
||||
out_file.write("\nSTATUS: {}\n".format(status))
|
||||
for key, event in events_by_key:
|
||||
out_file.write("key: {}\n".format(key))
|
||||
out_file.write("event: {}\n".format(event))
|
||||
|
||||
|
||||
class RawPickledFormatter(ResultsFormatter):
|
||||
"""Formats events as a pickled stream.
|
||||
|
||||
The parallel test runner has inferiors pickle their results and send them
|
||||
over a socket back to the parallel test. The parallel test runner then
|
||||
aggregates them into the final results formatter (e.g. xUnit).
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def arg_parser(cls):
|
||||
"""@return arg parser used to parse formatter-specific options."""
|
||||
parser = super(RawPickledFormatter, cls).arg_parser()
|
||||
return parser
|
||||
|
||||
def __init__(self, out_file, options):
|
||||
super(RawPickledFormatter, self).__init__(out_file, options)
|
||||
self.pid = os.getpid()
|
||||
|
||||
def handle_event(self, test_event):
|
||||
super(RawPickledFormatter, self).handle_event(test_event)
|
||||
|
||||
# Convert initialize/terminate events into job_begin/job_end events.
|
||||
event_type = test_event["event"]
|
||||
if event_type is None:
|
||||
return
|
||||
|
||||
if event_type == "initialize":
|
||||
test_event["event"] = "job_begin"
|
||||
elif event_type == "terminate":
|
||||
test_event["event"] = "job_end"
|
||||
|
||||
# Tack on the pid.
|
||||
test_event["pid"] = self.pid
|
||||
|
||||
# Send it as {serialized_length_of_serialized_bytes}{serialized_bytes}
|
||||
import struct
|
||||
msg = cPickle.dumps(test_event)
|
||||
packet = struct.pack("!I%ds" % len(msg), len(msg), msg)
|
||||
self.out_file.send(packet)
|
||||
|
||||
|
||||
class DumpFormatter(ResultsFormatter):
|
||||
"""Formats events to the file as their raw python dictionary format."""
|
||||
|
||||
def handle_event(self, test_event):
|
||||
super(DumpFormatter, self).handle_event(test_event)
|
||||
self.out_file.write("\n" + pprint.pformat(test_event) + "\n")
|
|
@ -8,8 +8,8 @@ Provides an xUnit ResultsFormatter for integrating the LLDB
|
|||
test suite with the Jenkins xUnit aggregator and other xUnit-compliant
|
||||
test output processors.
|
||||
"""
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
|
||||
# System modules
|
||||
import re
|
||||
|
@ -20,8 +20,8 @@ import xml.sax.saxutils
|
|||
import six
|
||||
|
||||
# Local modules
|
||||
from .result_formatter import EventBuilder
|
||||
from .result_formatter import ResultsFormatter
|
||||
from ..event_builder import EventBuilder
|
||||
from .results_formatter import ResultsFormatter
|
||||
|
||||
|
||||
class XunitFormatter(ResultsFormatter):
|
||||
|
@ -36,10 +36,10 @@ class XunitFormatter(ResultsFormatter):
|
|||
|
||||
@staticmethod
|
||||
def _build_illegal_xml_regex():
|
||||
"""Contructs a regex to match all illegal xml characters.
|
||||
"""Constructs a regex to match all illegal xml characters.
|
||||
|
||||
Expects to be used against a unicode string."""
|
||||
# Construct the range pairs of invalid unicode chareacters.
|
||||
# Construct the range pairs of invalid unicode characters.
|
||||
illegal_chars_u = [
|
||||
(0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x1F), (0x7F, 0x84),
|
||||
(0x86, 0x9F), (0xFDD0, 0xFDDF), (0xFFFE, 0xFFFF)]
|
||||
|
@ -139,10 +139,10 @@ class XunitFormatter(ResultsFormatter):
|
|||
|
||||
@staticmethod
|
||||
def _build_regex_list_from_patterns(patterns):
|
||||
"""Builds a list of compiled regexes from option value.
|
||||
"""Builds a list of compiled regular expressions from option value.
|
||||
|
||||
@param option string containing a comma-separated list of regex
|
||||
patterns. Zero-length or None will produce an empty regex list.
|
||||
@param patterns contains a list of regular expression
|
||||
patterns.
|
||||
|
||||
@return list of compiled regular expressions, empty if no
|
||||
patterns provided.
|
||||
|
@ -156,7 +156,7 @@ class XunitFormatter(ResultsFormatter):
|
|||
def __init__(self, out_file, options):
|
||||
"""Initializes the XunitFormatter instance.
|
||||
@param out_file file-like object where formatted output is written.
|
||||
@param options_dict specifies a dictionary of options for the
|
||||
@param options specifies a dictionary of options for the
|
||||
formatter.
|
||||
"""
|
||||
# Initialize the parent
|
||||
|
@ -198,9 +198,7 @@ class XunitFormatter(ResultsFormatter):
|
|||
self._handle_timeout
|
||||
}
|
||||
|
||||
RESULT_TYPES = set(
|
||||
[EventBuilder.TYPE_TEST_RESULT,
|
||||
EventBuilder.TYPE_JOB_RESULT])
|
||||
RESULT_TYPES = {EventBuilder.TYPE_TEST_RESULT, EventBuilder.TYPE_JOB_RESULT}
|
||||
|
||||
def handle_event(self, test_event):
|
||||
super(XunitFormatter, self).handle_event(test_event)
|
||||
|
@ -401,7 +399,8 @@ class XunitFormatter(ResultsFormatter):
|
|||
raise Exception(
|
||||
"unknown xfail option: {}".format(self.options.xfail))
|
||||
|
||||
def _handle_expected_timeout(self, test_event):
|
||||
@staticmethod
|
||||
def _handle_expected_timeout(test_event):
|
||||
"""Handles expected_timeout.
|
||||
@param test_event the test event to handle.
|
||||
"""
|
||||
|
@ -418,7 +417,7 @@ class XunitFormatter(ResultsFormatter):
|
|||
# test results viewer.
|
||||
result = self._common_add_testcase_entry(
|
||||
test_event,
|
||||
inner_content=("<unexpected-success />"))
|
||||
inner_content="<unexpected-success />")
|
||||
with self.lock:
|
||||
self.elements["unexpected_successes"].append(result)
|
||||
elif self.options.xpass == XunitFormatter.RM_SUCCESS:
|
||||
|
@ -519,7 +518,7 @@ class XunitFormatter(ResultsFormatter):
|
|||
|
||||
xUnit output is in XML. The reporting system cannot complete the
|
||||
formatting of the output without knowing when there is no more input.
|
||||
This call addresses notifcation of the completed test run and thus is
|
||||
This call addresses notification of the completed test run and thus is
|
||||
when we can finish off the report output.
|
||||
"""
|
||||
|
Loading…
Reference in New Issue