From f3b60130e2192547a633e28423ef8b1b40984532 Mon Sep 17 00:00:00 2001 From: Robert Guo Date: Tue, 6 Jun 2017 12:52:41 -0400 Subject: SERVER-25293 log hook testcases only to logkeeper --- buildscripts/burn_in_tests.py | 4 +- buildscripts/resmoke.py | 37 +++-- buildscripts/resmokelib/logging/loggers.py | 41 ++---- buildscripts/resmokelib/parser.py | 2 +- buildscripts/resmokelib/reportfile.py | 2 +- buildscripts/resmokelib/testing/executor.py | 57 ++++---- buildscripts/resmokelib/testing/suite.py | 198 +++++++++++++++++++++++---- buildscripts/resmokelib/testing/summary.py | 2 +- buildscripts/resmokelib/testing/testcases.py | 2 +- buildscripts/resmokelib/testing/testgroup.py | 192 -------------------------- 10 files changed, 228 insertions(+), 309 deletions(-) delete mode 100644 buildscripts/resmokelib/testing/testgroup.py 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 -- cgit v1.2.1