mirror of
https://github.com/mongodb/mongo.git
synced 2024-12-01 01:21:03 +01:00
240 lines
7.8 KiB
Python
Executable File
240 lines
7.8 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
"""
|
|
Command line utility for executing MongoDB tests of all kinds.
|
|
"""
|
|
|
|
from __future__ import absolute_import
|
|
|
|
import json
|
|
import os.path
|
|
import random
|
|
import signal
|
|
import sys
|
|
import time
|
|
import traceback
|
|
|
|
# Get relative imports to work when the package is not installed on the PYTHONPATH.
|
|
if __name__ == "__main__" and __package__ is None:
|
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
from buildscripts import resmokelib
|
|
|
|
|
|
def _execute_suite(suite, logging_config):
|
|
"""
|
|
Executes each test group of 'suite', failing fast if requested.
|
|
|
|
Returns true if the execution of the suite was interrupted by the
|
|
user, and false otherwise.
|
|
"""
|
|
|
|
logger = resmokelib.logging.loggers.EXECUTOR
|
|
|
|
for group in suite.test_groups:
|
|
if resmokelib.config.SHUFFLE:
|
|
logger.info("Shuffling order of tests for %ss in suite %s. The seed is %d.",
|
|
group.test_kind, suite.get_name(), resmokelib.config.RANDOM_SEED)
|
|
random.seed(resmokelib.config.RANDOM_SEED)
|
|
random.shuffle(group.tests)
|
|
|
|
if resmokelib.config.DRY_RUN == "tests":
|
|
sb = []
|
|
sb.append("Tests that would be run for %ss in suite %s:"
|
|
% (group.test_kind, suite.get_name()))
|
|
if len(group.tests) > 0:
|
|
for test in group.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
|
|
# that would get run by any other suites the user specified.
|
|
group.return_code = 0
|
|
continue
|
|
|
|
if len(group.tests) == 0:
|
|
logger.info("Skipping %ss, no tests to run", group.test_kind)
|
|
continue
|
|
|
|
group_config = suite.get_executor_config().get(group.test_kind, {})
|
|
executor = resmokelib.testing.executor.TestGroupExecutor(logger,
|
|
group,
|
|
logging_config,
|
|
**group_config)
|
|
|
|
try:
|
|
executor.run()
|
|
if resmokelib.config.FAIL_FAST and group.return_code != 0:
|
|
suite.return_code = group.return_code
|
|
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.",
|
|
group.test_kind, suite.get_name())
|
|
suite.return_code = 2
|
|
return False
|
|
|
|
|
|
def _log_summary(logger, suites, time_taken):
|
|
if len(suites) > 1:
|
|
sb = []
|
|
sb.append("Summary of all suites: %d suites ran in %0.2f seconds"
|
|
% (len(suites), time_taken))
|
|
for suite in suites:
|
|
suite_sb = []
|
|
suite.summarize(suite_sb)
|
|
sb.append(" %s: %s" % (suite.get_name(), "\n ".join(suite_sb)))
|
|
|
|
logger.info("=" * 80)
|
|
logger.info("\n".join(sb))
|
|
|
|
|
|
def _summarize_suite(suite):
|
|
sb = []
|
|
suite.summarize(sb)
|
|
return "\n".join(sb)
|
|
|
|
|
|
def _dump_suite_config(suite, logging_config):
|
|
"""
|
|
Returns a string that represents the YAML configuration of a suite.
|
|
|
|
TODO: include the "options" key in the result
|
|
"""
|
|
|
|
sb = []
|
|
sb.append("YAML configuration of suite %s" % (suite.get_name()))
|
|
sb.append(resmokelib.utils.dump_yaml({"selector": suite.get_selector_config()}))
|
|
sb.append("")
|
|
sb.append(resmokelib.utils.dump_yaml({"executor": suite.get_executor_config()}))
|
|
sb.append("")
|
|
sb.append(resmokelib.utils.dump_yaml({"logging": logging_config}))
|
|
return "\n".join(sb)
|
|
|
|
|
|
def find_suites_by_test(suites):
|
|
"""
|
|
Looks up what other resmoke suites run the tests specified in the suites
|
|
parameter. Returns a dict keyed by test name, value is array of suite names.
|
|
"""
|
|
|
|
memberships = {}
|
|
test_membership = resmokelib.parser.create_test_membership_map()
|
|
for suite in suites:
|
|
for group in suite.test_groups:
|
|
for test in group.tests:
|
|
memberships[test] = test_membership[test]
|
|
return memberships
|
|
|
|
def _write_report_file(suites, pathname):
|
|
"""
|
|
Writes the report.json file if requested.
|
|
"""
|
|
|
|
reports = []
|
|
for suite in suites:
|
|
for group in suite.test_groups:
|
|
reports.extend(group.get_reports())
|
|
|
|
combined_report_dict = resmokelib.testing.report.TestReport.combine(*reports).as_dict()
|
|
with open(pathname, "w") as fp:
|
|
json.dump(combined_report_dict, fp)
|
|
|
|
|
|
def main():
|
|
start_time = time.time()
|
|
|
|
values, args = resmokelib.parser.parse_command_line()
|
|
|
|
logging_config = resmokelib.parser.get_logging_config(values)
|
|
resmokelib.logging.config.apply_config(logging_config)
|
|
resmokelib.logging.flush.start_thread()
|
|
|
|
resmokelib.parser.update_config_vars(values)
|
|
|
|
exec_logger = resmokelib.logging.loggers.EXECUTOR
|
|
resmoke_logger = resmokelib.logging.loggers.new_logger("resmoke", parent=exec_logger)
|
|
|
|
if values.list_suites:
|
|
suite_names = resmokelib.parser.get_named_suites()
|
|
resmoke_logger.info("Suites available to execute:\n%s", "\n".join(suite_names))
|
|
sys.exit(0)
|
|
|
|
interrupted = False
|
|
suites = resmokelib.parser.get_suites(values, args)
|
|
|
|
# Run the suite finder after the test suite parsing is complete.
|
|
if values.find_suites:
|
|
suites_by_test = find_suites_by_test(suites)
|
|
for test in sorted(suites_by_test):
|
|
suite_names = suites_by_test[test]
|
|
resmoke_logger.info("%s will be run by the following suite(s): %s", test, suite_names)
|
|
sys.exit(0)
|
|
|
|
try:
|
|
for suite in suites:
|
|
resmoke_logger.info(_dump_suite_config(suite, logging_config))
|
|
|
|
suite.record_start()
|
|
interrupted = _execute_suite(suite, logging_config)
|
|
suite.record_end()
|
|
|
|
resmoke_logger.info("=" * 80)
|
|
resmoke_logger.info("Summary of %s suite: %s",
|
|
suite.get_name(), _summarize_suite(suite))
|
|
|
|
if interrupted or (resmokelib.config.FAIL_FAST and suite.return_code != 0):
|
|
time_taken = time.time() - start_time
|
|
_log_summary(resmoke_logger, suites, time_taken)
|
|
sys.exit(suite.return_code)
|
|
|
|
time_taken = time.time() - start_time
|
|
_log_summary(resmoke_logger, suites, time_taken)
|
|
|
|
# Exit with a nonzero code if any of the suites failed.
|
|
exit_code = max(suite.return_code for suite in suites)
|
|
sys.exit(exit_code)
|
|
finally:
|
|
if not interrupted:
|
|
resmokelib.logging.flush.stop_thread()
|
|
|
|
if resmokelib.config.REPORT_FILE is not None:
|
|
_write_report_file(suites, resmokelib.config.REPORT_FILE)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
def _dump_stacks(signum, frame):
|
|
"""
|
|
Signal handler that will dump the stacks of all threads.
|
|
"""
|
|
|
|
header_msg = "Dumping stacks due to SIGUSR1 signal"
|
|
|
|
sb = []
|
|
sb.append("=" * len(header_msg))
|
|
sb.append(header_msg)
|
|
sb.append("=" * len(header_msg))
|
|
|
|
frames = sys._current_frames()
|
|
sb.append("Total threads: %d" % (len(frames)))
|
|
sb.append("")
|
|
|
|
for thread_id in frames:
|
|
stack = frames[thread_id]
|
|
sb.append("Thread %d:" % (thread_id))
|
|
sb.append("".join(traceback.format_stack(stack)))
|
|
|
|
sb.append("=" * len(header_msg))
|
|
print "\n".join(sb)
|
|
|
|
try:
|
|
signal.signal(signal.SIGUSR1, _dump_stacks)
|
|
except AttributeError:
|
|
print "Cannot catch signals on Windows"
|
|
|
|
main()
|