summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Guo <robert.guo@10gen.com>2017-06-06 12:52:41 -0400
committerRobert Guo <robert.guo@10gen.com>2017-06-14 19:57:10 -0400
commitf3b60130e2192547a633e28423ef8b1b40984532 (patch)
tree4b859f620052ee2f26104f2b15c06a2427e6e807
parenta68f209e4ff495e805e4d1b12d6fc3381c56a771 (diff)
downloadmongo-f3b60130e2192547a633e28423ef8b1b40984532.tar.gz
SERVER-25293 log hook testcases only to logkeeper
-rw-r--r--buildscripts/burn_in_tests.py4
-rwxr-xr-xbuildscripts/resmoke.py37
-rw-r--r--buildscripts/resmokelib/logging/loggers.py41
-rw-r--r--buildscripts/resmokelib/parser.py2
-rw-r--r--buildscripts/resmokelib/reportfile.py2
-rw-r--r--buildscripts/resmokelib/testing/executor.py57
-rw-r--r--buildscripts/resmokelib/testing/suite.py198
-rw-r--r--buildscripts/resmokelib/testing/summary.py2
-rw-r--r--buildscripts/resmokelib/testing/testcases.py2
-rw-r--r--buildscripts/resmokelib/testing/testgroup.py192
10 files changed, 228 insertions, 309 deletions
diff --git a/buildscripts/burn_in_tests.py b/buildscripts/burn_in_tests.py
index 66b23bc480e..514a0cfeff4 100644
--- a/buildscripts/burn_in_tests.py
+++ b/buildscripts/burn_in_tests.py
@@ -242,7 +242,7 @@ def find_tests_by_executor(suites):
memberships = {}
test_membership = resmokelib.parser.create_test_membership_map()
for suite in suites:
- for test in suite.test_group.tests:
+ for test in suite.tests:
memberships[test] = test_membership[test]
return memberships
@@ -257,7 +257,7 @@ def create_executor_list(suites, exclude_suites):
memberships = collections.defaultdict(list)
test_membership = resmokelib.parser.create_test_membership_map()
for suite in suites:
- for test in suite.test_group.tests:
+ for test in suite.tests:
for executor in set(test_membership[test]) - set(exclude_suites):
memberships[executor].append(test)
return memberships
diff --git a/buildscripts/resmoke.py b/buildscripts/resmoke.py
index 3098b251e9b..79f3834f0bd 100755
--- a/buildscripts/resmoke.py
+++ b/buildscripts/resmoke.py
@@ -19,7 +19,7 @@ if __name__ == "__main__" and __package__ is None:
def _execute_suite(suite):
"""
- Executes each test group of 'suite', failing fast if requested.
+ Executes the test suite, failing fast if requested.
Returns true if the execution of the suite was interrupted by the
user, and false otherwise.
@@ -29,46 +29,43 @@ def _execute_suite(suite):
if resmokelib.config.SHUFFLE:
logger.info("Shuffling order of tests for %ss in suite %s. The seed is %d.",
- suite.test_group.test_kind, suite.get_name(), resmokelib.config.RANDOM_SEED)
+ suite.test_kind, suite.get_name(), resmokelib.config.RANDOM_SEED)
random.seed(resmokelib.config.RANDOM_SEED)
- random.shuffle(suite.test_group.tests)
+ random.shuffle(suite.tests)
if resmokelib.config.DRY_RUN == "tests":
sb = []
sb.append("Tests that would be run for %ss in suite %s:"
- % (suite.test_group.test_kind, suite.get_name()))
- if len(suite.test_group.tests) > 0:
- for test in suite.test_group.tests:
+ % (suite.test_kind, suite.get_name()))
+ if len(suite.tests) > 0:
+ for test in suite.tests:
sb.append(test)
else:
sb.append("(no tests)")
logger.info("\n".join(sb))
- # Set a successful return code on the test group because we want to output the tests
+ # Set a successful return code on the test suite because we want to output the tests
# that would get run by any other suites the user specified.
- suite.test_group.return_code = 0
+ suite.return_code = 0
return True
- if len(suite.test_group.tests) == 0:
- logger.info("Skipping %ss, no tests to run", suite.test_group.test_kind)
+ if len(suite.tests) == 0:
+ logger.info("Skipping %ss, no tests to run", suite.test_kind)
return True
- group_config = suite.get_executor_config()
- executor = resmokelib.testing.executor.TestGroupExecutor(logger,
- suite.test_group,
- **group_config)
+ suite_config = suite.get_executor_config()
+ executor = resmokelib.testing.executor.TestSuiteExecutor(logger, suite, **suite_config)
try:
executor.run()
- if resmokelib.config.FAIL_FAST and suite.test_group.return_code != 0:
- suite.return_code = suite.test_group.return_code
+ if resmokelib.config.FAIL_FAST and suite.return_code != 0:
return False
except resmokelib.errors.UserInterrupt:
suite.return_code = 130 # Simulate SIGINT as exit code.
return True
except:
logger.exception("Encountered an error when running %ss of suite %s.",
- suite.test_group.test_kind, suite.get_name())
+ suite.test_kind, suite.get_name())
suite.return_code = 2
return False
@@ -112,7 +109,7 @@ def find_suites_by_test(suites):
memberships = {}
test_membership = resmokelib.parser.create_test_membership_map()
for suite in suites:
- for test in suite.test_group.tests:
+ for test in suite.tests:
memberships[test] = test_membership[test]
return memberships
@@ -159,9 +156,9 @@ def main():
for suite in suites:
resmoke_logger.info(_dump_suite_config(suite, logging_config))
- suite.record_start()
+ suite.record_suite_start()
interrupted = _execute_suite(suite)
- suite.record_end()
+ suite.record_suite_end()
resmoke_logger.info("=" * 80)
resmoke_logger.info("Summary of %s suite: %s",
diff --git a/buildscripts/resmokelib/logging/loggers.py b/buildscripts/resmokelib/logging/loggers.py
index a0458e2a98c..821595b79c7 100644
--- a/buildscripts/resmokelib/logging/loggers.py
+++ b/buildscripts/resmokelib/logging/loggers.py
@@ -141,38 +141,24 @@ class ExecutorRootLogger(RootLogger):
"""Create a child logger of this logger with the name "resmoke"."""
return BaseLogger("resmoke", parent=self)
- def new_testgroup_logger(self, test_kind):
- """Create a new child TestGroupLogger."""
- return TestGroupLogger(test_kind, self, self.fixture_root_logger, self.tests_root_logger)
-
-
-class TestGroupLogger(BaseLogger):
- def __init__(self, test_kind, executor_root_logger, fixture_root_logger, tests_root_logger):
- """Initialize a TestGroupLogger which will be a child of the "executor" root logger.
-
- :param test_kind: a group test kind (e.g. js_test, db_test, cpp_unit_test, etc.).
- :param fixture_root_logger: the root logger for the fixture logs.
- :param tests_root_logger: the root logger for the tests logs.
- """
- BaseLogger.__init__(self, "executor:%s" % test_kind, parent=executor_root_logger)
- self.test_kind = test_kind
- self.fixture_root_logger = fixture_root_logger
- self.tests_root_logger = tests_root_logger
-
- def new_job_logger(self, job_num):
+ def new_job_logger(self, test_kind, job_num):
"""Create a new child JobLogger."""
- return JobLogger(self.test_kind, job_num, self, self.fixture_root_logger)
+ return JobLogger(test_kind, job_num, self, self.fixture_root_logger)
- def new_testqueue_logger(self):
+ def new_testqueue_logger(self, test_kind):
"""Create a new TestQueueLogger that will be a child of the "tests" root logger."""
- return TestQueueLogger(self.test_kind, self.tests_root_logger)
+ return TestQueueLogger(test_kind, self.tests_root_logger)
+
+ def new_hook_logger(self, behavior_class, job_num):
+ """Create a new child hook logger."""
+ return BaseLogger("%s:job%d" % (behavior_class, job_num), parent=self.tests_root_logger)
class JobLogger(BaseLogger):
def __init__(self, test_kind, job_num, parent, fixture_root_logger):
"""Initialize a JobLogger.
- :param test_kind: a group test kind (e.g. js_test, db_test, etc.).
+ :param test_kind: the test kind (e.g. js_test, db_test, etc.).
:param job_num: a job number.
:param fixture_root_logger: the root logger for the fixture logs.
"""
@@ -192,10 +178,6 @@ class JobLogger(BaseLogger):
else:
self.build_id = None
- def new_hook_logger(self, behavior_class, job_num):
- """Create a new child hook logger."""
- return BaseLogger("%s:job%d" % (behavior_class, job_num), parent=self)
-
def new_fixture_logger(self, fixture_class):
"""Create a new fixture logger that will be a child of the "fixture" root logger."""
return FixtureLogger(fixture_class, self.job_num, self.build_id, self.fixture_root_logger)
@@ -225,9 +207,6 @@ class TestLogger(BaseLogger):
:param url: the build logger URL endpoint for the test.
"""
name = "%s:%s" % (parent.name, test_name)
- # Changing the parent to the TESTS logger would allow us avoid logging the hook tests output
- # on both the task log and the test log.
- # TODO SERVER-25293: Change the parent to the tests root logger
BaseLogger.__init__(self, name, parent=parent)
self.url_endpoint = url
self._add_build_logger_handler(build_id, test_id)
@@ -329,7 +308,7 @@ class TestQueueLogger(BaseLogger):
def __init__(self, test_kind, tests_root_logger):
"""Initialize a TestQueueLogger.
- :param test_kind: a group test kind (e.g. js_test, db_test, cpp_unit_test, etc.).
+ :param test_kind: the test kind (e.g. js_test, db_test, cpp_unit_test, etc.).
:param tests_root_logger: the root logger for the tests logs.
"""
BaseLogger.__init__(self, test_kind, parent=tests_root_logger)
diff --git a/buildscripts/resmokelib/parser.py b/buildscripts/resmokelib/parser.py
index a2b5da4be4b..6c521057e68 100644
--- a/buildscripts/resmokelib/parser.py
+++ b/buildscripts/resmokelib/parser.py
@@ -321,7 +321,7 @@ def create_test_membership_map(fail_on_missing_selector=False):
continue
raise
- for testfile in suite.test_group.tests:
+ for testfile in suite.tests:
if isinstance(testfile, dict):
continue
test_membership[testfile].append(suite_name)
diff --git a/buildscripts/resmokelib/reportfile.py b/buildscripts/resmokelib/reportfile.py
index 918e99af306..11e43018087 100644
--- a/buildscripts/resmokelib/reportfile.py
+++ b/buildscripts/resmokelib/reportfile.py
@@ -21,7 +21,7 @@ def write(suites):
reports = []
for suite in suites:
- reports.extend(suite.test_group.get_reports())
+ reports.extend(suite.get_reports())
combined_report_dict = _report.TestReport.combine(*reports).as_dict(convert_failures=True)
with open(config.REPORT_FILE, "w") as fp:
diff --git a/buildscripts/resmokelib/testing/executor.py b/buildscripts/resmokelib/testing/executor.py
index 8e37331c4a5..7286b05aeb3 100644
--- a/buildscripts/resmokelib/testing/executor.py
+++ b/buildscripts/resmokelib/testing/executor.py
@@ -19,9 +19,9 @@ from .. import utils
from ..utils import queue as _queue
-class TestGroupExecutor(object):
+class TestSuiteExecutor(object):
"""
- Executes a test group.
+ Executes a test suite.
Responsible for setting up and tearing down the fixtures that the
tests execute against.
@@ -31,35 +31,33 @@ class TestGroupExecutor(object):
def __init__(self,
exec_logger,
- test_group,
+ suite,
config=None,
fixture=None,
hooks=None):
"""
- Initializes the TestGroupExecutor with the test group to run.
+ Initializes the TestSuiteExecutor with the test suite to run.
"""
-
- # Build a logger for executing this group of tests.
- self.testgroup_logger = exec_logger.new_testgroup_logger(test_group.test_kind)
+ self.logger = exec_logger
self.fixture_config = fixture
self.hooks_config = utils.default_if_none(hooks, [])
self.test_config = utils.default_if_none(config, {})
- self._test_group = test_group
+ self._suite = suite
# Must be done after getting buildlogger configuration.
self._jobs = [self._make_job(job_num) for job_num in xrange(_config.JOBS)]
def run(self):
"""
- Executes the test group.
+ Executes the test suite.
Any exceptions that occur during setting up or tearing down a
fixture are propagated.
"""
- self.testgroup_logger.info("Starting execution of %ss...", self._test_group.test_kind)
+ self.logger.info("Starting execution of %ss...", self._suite.test_kind)
return_code = 0
teardown_flag = None
@@ -73,7 +71,7 @@ class TestGroupExecutor(object):
test_queue = self._make_test_queue()
partial_reports = [job.report for job in self._jobs]
- self._test_group.record_start(partial_reports)
+ self._suite.record_test_start(partial_reports)
# Have the Job threads destroy their fixture during the final repetition after they
# finish running their last test. This avoids having a large number of processes
@@ -82,7 +80,7 @@ class TestGroupExecutor(object):
teardown_flag = threading.Event() if num_repeats == 1 else None
(report, interrupted) = self._run_tests(test_queue, teardown_flag)
- self._test_group.record_end(report)
+ self._suite.record_test_end(report)
# If the user triggered a KeyboardInterrupt, then we should stop.
if interrupted:
@@ -92,8 +90,8 @@ class TestGroupExecutor(object):
return_code = 2
sb = [] # String builder.
- self._test_group.summarize_latest(sb)
- self.testgroup_logger.info("Summary: %s", "\n ".join(sb))
+ self._suite.summarize_latest(sb)
+ self.logger.info("Summary: %s", "\n ".join(sb))
if not report.wasSuccessful():
return_code = 1
@@ -108,7 +106,7 @@ class TestGroupExecutor(object):
if not teardown_flag:
if not self._teardown_fixtures():
return_code = 2
- self._test_group.return_code = return_code
+ self._suite.return_code = return_code
def _setup_fixtures(self):
"""
@@ -118,7 +116,7 @@ class TestGroupExecutor(object):
try:
job.fixture.setup()
except:
- self.testgroup_logger.exception(
+ self.logger.exception(
"Encountered an error while setting up %s.", job.fixture)
return False
@@ -127,7 +125,7 @@ class TestGroupExecutor(object):
try:
job.fixture.await_ready()
except:
- self.testgroup_logger.exception(
+ self.logger.exception(
"Encountered an error while waiting for %s to be ready", job.fixture)
return False
return True
@@ -165,7 +163,7 @@ class TestGroupExecutor(object):
while not joined:
# Need to pass a timeout to join() so that KeyboardInterrupt exceptions
# are propagated.
- joined = test_queue.join(TestGroupExecutor._TIMEOUT)
+ joined = test_queue.join(TestSuiteExecutor._TIMEOUT)
except (KeyboardInterrupt, SystemExit):
interrupt_flag.set()
user_interrupted = True
@@ -179,7 +177,7 @@ class TestGroupExecutor(object):
# We cannot return 'interrupt_flag.is_set()' because the interrupt flag can be set by a Job
# instance if a test fails and it decides to drain the queue. We only want to raise a
- # StopExecution exception in TestGroupExecutor.run() if the user triggered the interrupt.
+ # StopExecution exception in TestSuiteExecutor.run() if the user triggered the interrupt.
return (combined_report, user_interrupted)
def _teardown_fixtures(self):
@@ -193,11 +191,10 @@ class TestGroupExecutor(object):
for job in self._jobs:
try:
if not job.fixture.teardown(finished=True):
- self.testgroup_logger.warn("Teardown of %s was not successful.", job.fixture)
+ self.logger.warn("Teardown of %s was not successful.", job.fixture)
success = False
except:
- self.testgroup_logger.exception("Encountered an error while tearing down %s.",
- job.fixture)
+ self.logger.exception("Encountered an error while tearing down %s.", job.fixture)
success = False
return success
@@ -217,7 +214,7 @@ class TestGroupExecutor(object):
return fixtures.make_fixture(fixture_class, fixture_logger, job_num, **fixture_config)
- def _make_hooks(self, job_num, fixture, job_logger):
+ def _make_hooks(self, job_num, fixture):
"""
Creates the custom behaviors for the job's fixture.
"""
@@ -228,7 +225,7 @@ class TestGroupExecutor(object):
behavior_config = behavior_config.copy()
behavior_class = behavior_config.pop("class")
- hook_logger = job_logger.new_hook_logger(behavior_class, job_num)
+ hook_logger = self.logger.new_hook_logger(behavior_class, job_num)
behavior = _hooks.make_custom_behavior(behavior_class,
hook_logger,
fixture,
@@ -242,10 +239,10 @@ class TestGroupExecutor(object):
Returns a Job instance with its own fixture, hooks, and test
report.
"""
- job_logger = self.testgroup_logger.new_job_logger(job_num)
+ job_logger = self.logger.new_job_logger(self._suite.test_kind, job_num)
fixture = self._make_fixture(job_num, job_logger)
- hooks = self._make_hooks(job_num, fixture, job_logger)
+ hooks = self._make_hooks(job_num, fixture)
report = _report.TestReport(job_logger)
@@ -259,12 +256,12 @@ class TestGroupExecutor(object):
that the test cases can be dispatched to multiple threads.
"""
- test_kind_logger = self.testgroup_logger.new_testqueue_logger()
+ test_queue_logger = self.logger.new_testqueue_logger(self._suite.test_kind)
# Put all the test cases in a queue.
queue = _queue.Queue()
- for test_name in self._test_group.tests:
- test_case = testcases.make_test_case(self._test_group.test_kind,
- test_kind_logger,
+ for test_name in self._suite.tests:
+ test_case = testcases.make_test_case(self._suite.test_kind,
+ test_queue_logger,
test_name,
**self.test_config)
queue.put(test_case)
diff --git a/buildscripts/resmokelib/testing/suite.py b/buildscripts/resmokelib/testing/suite.py
index 11f201c69cc..1b492314185 100644
--- a/buildscripts/resmokelib/testing/suite.py
+++ b/buildscripts/resmokelib/testing/suite.py
@@ -1,20 +1,22 @@
"""
-Holder for a set of TestGroup instances.
+Holder for the (test kind, list of tests) pair with additional metadata about when and how they
+execute.
"""
from __future__ import absolute_import
+import itertools
import time
+from . import report as _report
from . import summary as _summary
-from . import testgroup
from .. import config as _config
from .. import selector as _selector
class Suite(object):
"""
- A suite of tests.
+ A suite of tests of a particular kind (e.g. C++ unit tests, dbtests, jstests).
"""
def __init__(self, suite_name, suite_config):
@@ -25,16 +27,23 @@ class Suite(object):
self._suite_name = suite_name
self._suite_config = suite_config
- test_kind = self.get_test_kind_config()
- tests = self._get_tests_for_group(test_kind)
- self.test_group = testgroup.TestGroup(test_kind, tests)
+ self.test_kind = self.get_test_kind_config()
+ self.tests = self._get_tests_for_kind(self.test_kind)
- self.return_code = None
+ self.return_code = None # Set by the executor.
- self._start_time = None
- self._end_time = None
+ self._suite_start_time = None
+ self._suite_end_time = None
- def _get_tests_for_group(self, test_kind):
+ self._test_start_times = []
+ self._test_end_times = []
+ self._reports = []
+
+ # We keep a reference to the TestReports from the currently running jobs so that we can
+ # report intermediate results.
+ self._partial_reports = None
+
+ def _get_tests_for_kind(self, test_kind):
"""
Returns the tests to run based on the 'test_kind'-specific
filtering policy.
@@ -86,26 +95,33 @@ class Suite(object):
"""
return self._suite_config["test_kind"]
- def record_start(self):
+ def record_suite_start(self):
"""
Records the start time of the suite.
"""
- self._start_time = time.time()
+ self._suite_start_time = time.time()
- def record_end(self):
+ def record_suite_end(self):
"""
Records the end time of the suite.
-
- Sets the 'return_code' of the suite based on the record codes of
- each of the individual test groups.
"""
+ self._suite_end_time = time.time()
- self._end_time = time.time()
+ def record_test_start(self, partial_reports):
+ """
+ Records the start time of an execution and stores the
+ TestReports for currently running jobs.
+ """
+ self._test_start_times.append(time.time())
+ self._partial_reports = partial_reports
- # Only set 'return_code' if it hasn't been set already. It may have been set if there was
- # an exception that happened during the execution of the suite.
- if self.return_code is None:
- self.return_code = self.test_group.return_code
+ def record_test_end(self, report):
+ """
+ Records the end time of an execution.
+ """
+ self._test_end_times.append(time.time())
+ self._reports.append(report)
+ self._partial_reports = None
def interrupt(self):
"""
@@ -114,21 +130,50 @@ class Suite(object):
Used when handling SIGUSR1 interrupts.
"""
- if self._end_time:
+ if self._suite_end_time:
return
- self.record_end()
- self.test_group.interrupt()
+ self.record_suite_end()
+
+ # Converts any partial reports to completed reports and ensures that report is ended.
+ active_report = self.get_active_report()
+ if active_report:
+ self.record_test_end(active_report)
+
+ def get_active_report(self):
+ """
+ Returns the partial report of the currently running execution, if there is one.
+ """
+ if not self._partial_reports:
+ return None
+ return _report.TestReport.combine(*self._partial_reports)
+
+ def get_reports(self):
+ """
+ Returns the list of reports. If there's an execution currently
+ in progress, then a report for the partial results is included
+ in the returned list.
+ """
+
+ if self._partial_reports is not None:
+ return self._reports + [self.get_active_report()]
+
+ return self._reports
def summarize(self, sb):
"""
- Appends a summary of the test group onto the string builder 'sb'.
+ Appends a summary of the suite onto the string builder 'sb'.
"""
- summary = _summary.Summary(0, 0.0, 0, 0, 0, 0)
+ if not self._reports:
+ sb.append("No tests ran.")
+ summary = _summary.Summary(0, 0.0, 0, 0, 0, 0)
+ elif len(self._reports) == 1:
+ summary = self._summarize_execution(0, sb)
+ else:
+ summary = self._summarize_repeated(sb)
- summary = self.test_group.summarize(sb)
- summarized_group = " %ss: %s" % (self.test_group.test_kind, "\n ".join(sb))
+ summarized_group = " %ss: %s" % (self.test_kind, "\n ".join(sb))
if summary.num_run == 0:
sb.append("Suite did not run any tests.")
@@ -136,8 +181,8 @@ class Suite(object):
# Override the 'time_taken' attribute of the summary if we have more accurate timing
# information available.
- if self._start_time is not None and self._end_time is not None:
- time_taken = self._end_time - self._start_time
+ if self._suite_start_time is not None and self._suite_end_time is not None:
+ time_taken = self._suite_end_time - self._suite_start_time
summary = summary._replace(time_taken=time_taken)
sb.append("%d test(s) ran in %0.2f seconds"
@@ -145,6 +190,99 @@ class Suite(object):
sb.append(summarized_group)
+ def summarize_latest(self, sb):
+ """
+ Returns a summary of the latest execution of the suite and appends a
+ summary of that execution onto the string builder 'sb'.
+
+ If there's an execution currently in progress, then the partial
+ summary of that execution is appended to 'sb'.
+ """
+
+ if self._partial_reports is None:
+ return self._summarize_execution(-1, sb)
+
+ active_report = _report.TestReport.combine(*self._partial_reports)
+ # Use the current time as the time that this suite finished running.
+ end_time = time.time()
+ return self._summarize_report(active_report, self._test_start_times[-1], end_time, sb)
+
+ def _summarize_repeated(self, sb):
+ """
+ Returns the summary information of all executions and appends
+ each execution's summary onto the string builder 'sb'. Also
+ appends information of how many repetitions there were.
+ """
+
+ num_iterations = len(self._reports)
+ total_time_taken = self._test_end_times[-1] - self._test_start_times[0]
+ sb.append("Executed %d times in %0.2f seconds:" % (num_iterations, total_time_taken))
+
+ combined_summary = _summary.Summary(0, 0.0, 0, 0, 0, 0)
+ for iteration in xrange(num_iterations):
+ # Summarize each execution as a bulleted list of results.
+ bulleter_sb = []
+ summary = self._summarize_execution(iteration, bulleter_sb)
+ combined_summary = _summary.combine(combined_summary, summary)
+
+ for (i, line) in enumerate(bulleter_sb):
+ # Only bullet first line, indent others.
+ prefix = "* " if i == 0 else " "
+ sb.append(prefix + line)
+
+ return combined_summary
+
+ def _summarize_execution(self, iteration, sb):
+ """
+ Returns the summary information of the execution given by
+ 'iteration' and appends a summary of that execution onto the
+ string builder 'sb'.
+ """
+
+ return self._summarize_report(self._reports[iteration],
+ self._test_start_times[iteration],
+ self._test_end_times[iteration],
+ sb)
+
+ def _summarize_report(self, report, start_time, end_time, sb):
+ """
+ Returns the summary information of the execution given by
+ 'report' that started at 'start_time' and finished at
+ 'end_time', and appends a summary of that execution onto the
+ string builder 'sb'.
+ """
+
+ time_taken = end_time - start_time
+
+ # Tests that were interrupted are treated as failures because (1) the test has already been
+ # started and therefore isn't skipped and (2) the test has yet to finish and therefore
+ # cannot be said to have succeeded.
+ num_failed = report.num_failed + report.num_interrupted
+ num_run = report.num_succeeded + report.num_errored + num_failed
+ num_skipped = len(self.tests) + report.num_dynamic - num_run
+
+ if report.num_succeeded == num_run and num_skipped == 0:
+ sb.append("All %d test(s) passed in %0.2f seconds." % (num_run, time_taken))
+ return _summary.Summary(num_run, time_taken, num_run, 0, 0, 0)
+
+ summary = _summary.Summary(num_run, time_taken, report.num_succeeded, num_skipped,
+ num_failed, report.num_errored)
+
+ sb.append("%d test(s) ran in %0.2f seconds"
+ " (%d succeeded, %d were skipped, %d failed, %d errored)" % summary)
+
+ if num_failed > 0:
+ sb.append("The following tests failed (with exit code):")
+ for test_info in itertools.chain(report.get_failed(), report.get_interrupted()):
+ sb.append(" %s (%d)" % (test_info.test_id, test_info.return_code))
+
+ if report.num_errored > 0:
+ sb.append("The following tests had errors:")
+ for test_info in report.get_errored():
+ sb.append(" %s" % (test_info.test_id))
+
+ return summary
+
@staticmethod
def log_summaries(logger, suites, time_taken):
sb = []
diff --git a/buildscripts/resmokelib/testing/summary.py b/buildscripts/resmokelib/testing/summary.py
index 1dae9ca81d6..bb44472caa4 100644
--- a/buildscripts/resmokelib/testing/summary.py
+++ b/buildscripts/resmokelib/testing/summary.py
@@ -1,5 +1,5 @@
"""
-Holder for summary information about a test group or suite.
+Holder for summary information about a test suite.
"""
from __future__ import absolute_import
diff --git a/buildscripts/resmokelib/testing/testcases.py b/buildscripts/resmokelib/testing/testcases.py
index d1d5a914dbd..060ee54591e 100644
--- a/buildscripts/resmokelib/testing/testcases.py
+++ b/buildscripts/resmokelib/testing/testcases.py
@@ -48,7 +48,7 @@ class TestCase(unittest.TestCase):
if not isinstance(test_name, basestring):
raise TypeError("test_name must be a string")
- # When the TestCase is created by the TestGroupExecutor (through a call to make_test_case())
+ # When the TestCase is created by the TestSuiteExecutor (through a call to make_test_case())
# logger is an instance of TestQueueLogger. When the TestCase is created by a hook
# implementation it is an instance of BaseLogger.
self.logger = logger
diff --git a/buildscripts/resmokelib/testing/testgroup.py b/buildscripts/resmokelib/testing/testgroup.py
deleted file mode 100644
index 69cdaf1de51..00000000000
--- a/buildscripts/resmokelib/testing/testgroup.py
+++ /dev/null
@@ -1,192 +0,0 @@
-"""
-Holder for the (test kind, list of tests) pair with additional metadata
-about when and how they execute.
-"""
-
-from __future__ import absolute_import
-
-import itertools
-import time
-
-from . import report as _report
-from . import summary as _summary
-
-
-class TestGroup(object):
- """
- A class to encapsulate the results of running a group of tests
- of a particular kind (e.g. C++ unit tests, dbtests, jstests).
- """
-
- def __init__(self, test_kind, tests):
- """
- Initializes the TestGroup with a list of tests.
- """
-
- self.test_kind = test_kind
- self.tests = tests
-
- self.return_code = None # Set by the executor.
-
- self._start_times = []
- self._end_times = []
- self._reports = []
-
- # We keep a reference to the TestReports from the currently running jobs so that we can
- # report intermediate results.
- self._partial_reports = None
-
- def get_active_report(self):
- """
- Returns the partial report of the currently running execution, if there is one.
- """
- if not self._partial_reports:
- return None
- return _report.TestReport.combine(*self._partial_reports)
-
- def get_reports(self):
- """
- Returns the list of reports. If there's an execution currently
- in progress, then a report for the partial results is included
- in the returned list.
- """
-
- if self._partial_reports is not None:
- return self._reports + [self.get_active_report()]
-
- return self._reports
-
- def record_start(self, partial_reports):
- """
- Records the start time of an execution and stores the
- TestReports for currently running jobs.
- """
- self._start_times.append(time.time())
- self._partial_reports = partial_reports
-
- def record_end(self, report):
- """
- Records the end time of an execution.
- """
- self._end_times.append(time.time())
- self._reports.append(report)
- self._partial_reports = None
-
- def interrupt(self):
- """
- Converts any partial reports to completed reports and ensures that report is ended.
-
- Called from Suite.interrupt() when handling a SIGUSR1 interrupt.
- """
-
- active_report = self.get_active_report()
- if active_report:
- self.record_end(active_report)
-
- def summarize_latest(self, sb):
- """
- Returns a summary of the latest execution of the group and appends a
- summary of that execution onto the string builder 'sb'.
-
- If there's an execution currently in progress, then the partial
- summary of that execution is appended to 'sb'.
- """
-
- if self._partial_reports is None:
- return self._summarize_execution(-1, sb)
-
- active_report = _report.TestReport.combine(*self._partial_reports)
- # Use the current time as the time that the test group finished running.
- end_time = time.time()
- return self._summarize_report(active_report, self._start_times[-1], end_time, sb)
-
- def summarize(self, sb):
- """
- Returns a summary of the execution(s) of the group and appends a
- summary of the execution(s) onto the string builder 'sb'.
- """
-
- if not self._reports:
- sb.append("No tests ran.")
- return _summary.Summary(0, 0.0, 0, 0, 0, 0)
-
- if len(self._reports) == 1:
- return self._summarize_execution(0, sb)
-
- return self._summarize_repeated(sb)
-
- def _summarize_repeated(self, sb):
- """
- Returns the summary information of all executions and appends
- each execution's summary onto the string builder 'sb'. Also
- appends information of how many repetitions there were.
- """
-
- num_iterations = len(self._reports)
- total_time_taken = self._end_times[-1] - self._start_times[0]
- sb.append("Executed %d times in %0.2f seconds:" % (num_iterations, total_time_taken))
-
- combined_summary = _summary.Summary(0, 0.0, 0, 0, 0, 0)
- for iteration in xrange(num_iterations):
- # Summarize each execution as a bulleted list of results.
- bulleter_sb = []
- summary = self._summarize_execution(iteration, bulleter_sb)
- combined_summary = _summary.combine(combined_summary, summary)
-
- for (i, line) in enumerate(bulleter_sb):
- # Only bullet first line, indent others.
- prefix = "* " if i == 0 else " "
- sb.append(prefix + line)
-
- return combined_summary
-
- def _summarize_execution(self, iteration, sb):
- """
- Returns the summary information of the execution given by
- 'iteration' and appends a summary of that execution onto the
- string builder 'sb'.
- """
-
- return self._summarize_report(self._reports[iteration],
- self._start_times[iteration],
- self._end_times[iteration],
- sb)
-
- def _summarize_report(self, report, start_time, end_time, sb):
- """
- Returns the summary information of the execution given by
- 'report' that started at 'start_time' and finished at
- 'end_time', and appends a summary of that execution onto the
- string builder 'sb'.
- """
-
- time_taken = end_time - start_time
-
- # Tests that were interrupted are treated as failures because (1) the test has already been
- # started and therefore isn't skipped and (2) the test has yet to finish and therefore
- # cannot be said to have succeeded.
- num_failed = report.num_failed + report.num_interrupted
- num_run = report.num_succeeded + report.num_errored + num_failed
- num_skipped = len(self.tests) + report.num_dynamic - num_run
-
- if report.num_succeeded == num_run and num_skipped == 0:
- sb.append("All %d test(s) passed in %0.2f seconds." % (num_run, time_taken))
- return _summary.Summary(num_run, time_taken, num_run, 0, 0, 0)
-
- summary = _summary.Summary(num_run, time_taken, report.num_succeeded, num_skipped,
- num_failed, report.num_errored)
-
- sb.append("%d test(s) ran in %0.2f seconds"
- " (%d succeeded, %d were skipped, %d failed, %d errored)" % summary)
-
- if num_failed > 0:
- sb.append("The following tests failed (with exit code):")
- for test_info in itertools.chain(report.get_failed(), report.get_interrupted()):
- sb.append(" %s (%d)" % (test_info.test_id, test_info.return_code))
-
- if report.num_errored > 0:
- sb.append("The following tests had errors:")
- for test_info in report.get_errored():
- sb.append(" %s" % (test_info.test_id))
-
- return summary