1#!/usr/bin/env python 2 3""" 4Command line utility for executing MongoDB tests of all kinds. 5""" 6 7from __future__ import absolute_import 8 9import json 10import os.path 11import random 12import signal 13import sys 14import time 15import traceback 16 17# Get relative imports to work when the package is not installed on the PYTHONPATH. 18if __name__ == "__main__" and __package__ is None: 19 sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 20 from buildscripts import resmokelib 21 22 23def _execute_suite(suite, logging_config): 24 """ 25 Executes each test group of 'suite', failing fast if requested. 26 27 Returns true if the execution of the suite was interrupted by the 28 user, and false otherwise. 29 """ 30 31 logger = resmokelib.logging.loggers.EXECUTOR 32 33 for group in suite.test_groups: 34 if resmokelib.config.SHUFFLE: 35 logger.info("Shuffling order of tests for %ss in suite %s. The seed is %d.", 36 group.test_kind, suite.get_name(), resmokelib.config.RANDOM_SEED) 37 random.seed(resmokelib.config.RANDOM_SEED) 38 random.shuffle(group.tests) 39 40 if resmokelib.config.DRY_RUN == "tests": 41 sb = [] 42 sb.append("Tests that would be run for %ss in suite %s:" 43 % (group.test_kind, suite.get_name())) 44 if len(group.tests) > 0: 45 for test in group.tests: 46 sb.append(test) 47 else: 48 sb.append("(no tests)") 49 logger.info("\n".join(sb)) 50 51 # Set a successful return code on the test group because we want to output the tests 52 # that would get run by any other suites the user specified. 53 group.return_code = 0 54 continue 55 56 if len(group.tests) == 0: 57 logger.info("Skipping %ss, no tests to run", group.test_kind) 58 continue 59 60 group_config = suite.get_executor_config().get(group.test_kind, {}) 61 executor = resmokelib.testing.executor.TestGroupExecutor(logger, 62 group, 63 logging_config, 64 **group_config) 65 66 try: 67 executor.run() 68 if resmokelib.config.FAIL_FAST and group.return_code != 0: 69 suite.return_code = group.return_code 70 return False 71 except resmokelib.errors.UserInterrupt: 72 suite.return_code = 130 # Simulate SIGINT as exit code. 73 return True 74 except: 75 logger.exception("Encountered an error when running %ss of suite %s.", 76 group.test_kind, suite.get_name()) 77 suite.return_code = 2 78 return False 79 80 81def _log_summary(logger, suites, time_taken): 82 if len(suites) > 1: 83 sb = [] 84 sb.append("Summary of all suites: %d suites ran in %0.2f seconds" 85 % (len(suites), time_taken)) 86 for suite in suites: 87 suite_sb = [] 88 suite.summarize(suite_sb) 89 sb.append(" %s: %s" % (suite.get_name(), "\n ".join(suite_sb))) 90 91 logger.info("=" * 80) 92 logger.info("\n".join(sb)) 93 94 95def _summarize_suite(suite): 96 sb = [] 97 suite.summarize(sb) 98 return "\n".join(sb) 99 100 101def _dump_suite_config(suite, logging_config): 102 """ 103 Returns a string that represents the YAML configuration of a suite. 104 105 TODO: include the "options" key in the result 106 """ 107 108 sb = [] 109 sb.append("YAML configuration of suite %s" % (suite.get_name())) 110 sb.append(resmokelib.utils.dump_yaml({"selector": suite.get_selector_config()})) 111 sb.append("") 112 sb.append(resmokelib.utils.dump_yaml({"executor": suite.get_executor_config()})) 113 sb.append("") 114 sb.append(resmokelib.utils.dump_yaml({"logging": logging_config})) 115 return "\n".join(sb) 116 117 118def _write_report_file(suites, pathname): 119 """ 120 Writes the report.json file if requested. 121 """ 122 123 reports = [] 124 for suite in suites: 125 for group in suite.test_groups: 126 reports.extend(group.get_reports()) 127 128 combined_report_dict = resmokelib.testing.report.TestReport.combine(*reports).as_dict() 129 with open(pathname, "w") as fp: 130 json.dump(combined_report_dict, fp) 131 132 133def main(): 134 start_time = time.time() 135 136 values, args = resmokelib.parser.parse_command_line() 137 138 logging_config = resmokelib.parser.get_logging_config(values) 139 resmokelib.logging.config.apply_config(logging_config) 140 resmokelib.logging.flush.start_thread() 141 142 resmokelib.parser.update_config_vars(values) 143 144 exec_logger = resmokelib.logging.loggers.EXECUTOR 145 resmoke_logger = resmokelib.logging.loggers.new_logger("resmoke", parent=exec_logger) 146 147 if values.list_suites: 148 suite_names = resmokelib.parser.get_named_suites() 149 resmoke_logger.info("Suites available to execute:\n%s", "\n".join(suite_names)) 150 sys.exit(0) 151 152 interrupted = False 153 suites = resmokelib.parser.get_suites(values, args) 154 try: 155 for suite in suites: 156 resmoke_logger.info(_dump_suite_config(suite, logging_config)) 157 158 suite.record_start() 159 interrupted = _execute_suite(suite, logging_config) 160 suite.record_end() 161 162 resmoke_logger.info("=" * 80) 163 resmoke_logger.info("Summary of %s suite: %s", 164 suite.get_name(), _summarize_suite(suite)) 165 166 if interrupted or (resmokelib.config.FAIL_FAST and suite.return_code != 0): 167 time_taken = time.time() - start_time 168 _log_summary(resmoke_logger, suites, time_taken) 169 sys.exit(suite.return_code) 170 171 time_taken = time.time() - start_time 172 _log_summary(resmoke_logger, suites, time_taken) 173 174 # Exit with a nonzero code if any of the suites failed. 175 exit_code = max(suite.return_code for suite in suites) 176 sys.exit(exit_code) 177 finally: 178 if not interrupted: 179 resmokelib.logging.flush.stop_thread() 180 181 if resmokelib.config.REPORT_FILE is not None: 182 _write_report_file(suites, resmokelib.config.REPORT_FILE) 183 184 185if __name__ == "__main__": 186 187 def _dump_stacks(signum, frame): 188 """ 189 Signal handler that will dump the stacks of all threads. 190 """ 191 192 header_msg = "Dumping stacks due to SIGUSR1 signal" 193 194 sb = [] 195 sb.append("=" * len(header_msg)) 196 sb.append(header_msg) 197 sb.append("=" * len(header_msg)) 198 199 frames = sys._current_frames() 200 sb.append("Total threads: %d" % (len(frames))) 201 sb.append("") 202 203 for thread_id in frames: 204 stack = frames[thread_id] 205 sb.append("Thread %d:" % (thread_id)) 206 sb.append("".join(traceback.format_stack(stack))) 207 208 sb.append("=" * len(header_msg)) 209 print "\n".join(sb) 210 211 try: 212 signal.signal(signal.SIGUSR1, _dump_stacks) 213 except AttributeError: 214 print "Cannot catch signals on Windows" 215 216 main() 217