Skip to content

Commit 8b3694d

Browse files
committed
Revert "SERVER-29522 better error codes when running concurrent tests in resmoke"
This reverts commit 1e26264.
1 parent 9489bb5 commit 8b3694d

File tree

2 files changed

+70
-152
lines changed

2 files changed

+70
-152
lines changed

buildscripts/resmokelib/testing/job.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def _execute_test(self, test):
9494
Calls the before/after test hooks and executes 'test'.
9595
"""
9696

97-
test.configure(self.fixture)
97+
test.configure(self.fixture, config.NUM_CLIENTS_PER_FIXTURE)
9898
self._run_hooks_before_tests(test)
9999

100100
test(self.report)

buildscripts/resmokelib/testing/testcases/jstest.py

Lines changed: 69 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -14,44 +14,54 @@
1414
from ... import config
1515
from ... import core
1616
from ... import utils
17-
from ...utils import registry
1817

1918

20-
class _SingleJSTestCase(interface.TestCase):
19+
class JSTestCase(interface.TestCase):
2120
"""
2221
A jstest to execute.
2322
"""
2423

25-
REGISTERED_NAME = registry.LEAVE_UNREGISTERED
24+
REGISTERED_NAME = "js_test"
25+
26+
# A wrapper for the thread class that lets us propagate exceptions.
27+
class ExceptionThread(threading.Thread):
28+
def __init__(self, my_target, my_args):
29+
threading.Thread.__init__(self, target=my_target, args=my_args)
30+
self.err = None
31+
32+
def run(self):
33+
try:
34+
threading.Thread.run(self)
35+
except:
36+
self.err = sys.exc_info()[1]
37+
else:
38+
self.err = None
39+
40+
def _get_exception(self):
41+
return self.err
42+
43+
DEFAULT_CLIENT_NUM = 1
2644

2745
def __init__(self,
2846
logger,
2947
js_filename,
3048
shell_executable=None,
31-
shell_options=None):
32-
"""
33-
Initializes the _SingleJSTestCase with the JS file to run.
34-
"""
49+
shell_options=None,
50+
test_kind="JSTest"):
51+
"""Initializes the JSTestCase with the JS file to run."""
3552

36-
interface.TestCase.__init__(self, logger, "JSTest", js_filename)
53+
interface.TestCase.__init__(self, logger, test_kind, js_filename)
3754

3855
# Command line options override the YAML configuration.
3956
self.shell_executable = utils.default_if_none(config.MONGO_EXECUTABLE, shell_executable)
4057

4158
self.js_filename = js_filename
4259
self.shell_options = utils.default_if_none(shell_options, {}).copy()
60+
self.num_clients = JSTestCase.DEFAULT_CLIENT_NUM
4361

44-
def configure(self, fixture, *args, **kwargs):
62+
def configure(self, fixture, num_clients=DEFAULT_CLIENT_NUM, *args, **kwargs):
4563
interface.TestCase.configure(self, fixture, *args, **kwargs)
4664

47-
def configure_shell(self):
48-
"""
49-
Sets up the global variables for the shell, and data/ directory for the mongod.
50-
51-
configure_shell() only needs to be called once per test. Therefore if creating multiple
52-
_SingleJSTestCase instances to be run in parallel, only call configure_shell() on one of
53-
them.
54-
"""
5565
global_vars = self.shell_options.get("global_vars", {}).copy()
5666
data_dir = self._get_data_dir(global_vars)
5767

@@ -76,14 +86,16 @@ def configure_shell(self):
7686
global_vars["MongoRunner.mongoShellPath"] = self.shell_executable
7787

7888
test_data = global_vars.get("TestData", {}).copy()
79-
test_data["minPort"] = core.network.PortAllocator.min_test_port(self.fixture.job_num)
80-
test_data["maxPort"] = core.network.PortAllocator.max_test_port(self.fixture.job_num)
89+
test_data["minPort"] = core.network.PortAllocator.min_test_port(fixture.job_num)
90+
test_data["maxPort"] = core.network.PortAllocator.max_test_port(fixture.job_num)
8191

8292
global_vars["TestData"] = test_data
8393
self.shell_options["global_vars"] = global_vars
8494

8595
shutil.rmtree(data_dir, ignore_errors=True)
8696

97+
self.num_clients = num_clients
98+
8799
try:
88100
os.makedirs(data_dir)
89101
except os.error:
@@ -118,78 +130,34 @@ def _get_data_dir(self, global_vars):
118130
config.MONGO_RUNNER_SUBDIR)
119131

120132
def run_test(self):
133+
threads = []
121134
try:
122-
shell = self._make_process()
123-
self._execute(shell)
135+
# Don't thread if there is only one client.
136+
if self.num_clients == 1:
137+
shell = self._make_process(self.logger)
138+
self._execute(shell)
139+
else:
140+
# If there are multiple clients, make a new thread for each client.
141+
for i in xrange(self.num_clients):
142+
t = self.ExceptionThread(my_target=self._run_test_in_thread, my_args=[i])
143+
t.start()
144+
threads.append(t)
124145
except self.failureException:
125146
raise
126147
except:
127148
self.logger.exception("Encountered an error running jstest %s.", self.basename())
128149
raise
129-
130-
def _make_process(self):
131-
return core.programs.mongo_shell_program(
132-
self.logger,
133-
executable=self.shell_executable,
134-
filename=self.js_filename,
135-
connection_string=self.fixture.get_driver_connection_url(),
136-
**self.shell_options)
137-
138-
139-
class JSTestCase(interface.TestCase):
140-
"""
141-
A wrapper for several copies of a SingleJSTest to execute.
142-
"""
143-
144-
REGISTERED_NAME = "js_test"
145-
146-
class ThreadWithException(threading.Thread):
147-
"""
148-
A wrapper for the thread class that lets us propagate exceptions.
149-
"""
150-
151-
def __init__(self, *args, **kwargs):
152-
threading.Thread.__init__(self, *args, **kwargs)
153-
self.exc_info = None
154-
155-
def run(self):
156-
try:
157-
threading.Thread.run(self)
158-
except:
159-
self.exc_info = sys.exc_info()
160-
161-
def __init__(self,
162-
logger,
163-
js_filename,
164-
shell_executable=None,
165-
shell_options=None,
166-
test_kind="JSTest"):
167-
"""
168-
Initializes the JSTestCase with the JS file to run.
169-
"""
170-
171-
interface.TestCase.__init__(self, logger, test_kind, js_filename)
172-
173-
self.num_clients = config.NUM_CLIENTS_PER_FIXTURE
174-
self.test_case_template = _SingleJSTestCase(logger, js_filename, shell_executable,
175-
shell_options)
176-
177-
def configure(self, fixture, *args, **kwargs):
178-
interface.TestCase.configure(self, fixture, *args, **kwargs)
179-
self.test_case_template.configure(fixture, *args, **kwargs)
180-
self.test_case_template.configure_shell()
181-
182-
def _make_process(self):
183-
# This function should only be called by interface.py's as_command().
184-
return self.test_case_template._make_process()
185-
186-
def _get_shell_options_for_thread(self, thread_id):
187-
"""
188-
Get shell_options with an initialized TestData object for given thread.
189-
"""
190-
191-
# We give each _SingleJSTestCase its own copy of the shell_options.
192-
shell_options = self.test_case_template.shell_options.copy()
150+
finally:
151+
for t in threads:
152+
t.join()
153+
for t in threads:
154+
if t._get_exception() is not None:
155+
raise t._get_exception()
156+
157+
def _make_process(self, logger=None, thread_id=0):
158+
# Since _make_process() is called by each thread, we make a shallow copy of the mongo shell
159+
# options to avoid modifying the shared options for the JSTestCase.
160+
shell_options = self.shell_options.copy()
193161
global_vars = shell_options["global_vars"].copy()
194162
test_data = global_vars["TestData"].copy()
195163

@@ -204,70 +172,20 @@ def _get_shell_options_for_thread(self, thread_id):
204172
global_vars["TestData"] = test_data
205173
shell_options["global_vars"] = global_vars
206174

207-
return shell_options
175+
# If logger is none, it means that it's not running in a thread and thus logger should be
176+
# set to self.logger.
177+
logger = utils.default_if_none(logger, self.logger)
208178

209-
def _create_test_case_for_thread(self, logger, thread_id):
210-
"""
211-
Create and configure a _SingleJSTestCase to be run in a separate thread.
212-
"""
213-
214-
shell_options = self._get_shell_options_for_thread(thread_id)
215-
test_case = _SingleJSTestCase(logger,
216-
self.test_case_template.js_filename,
217-
self.test_case_template.shell_executable,
218-
shell_options)
219-
220-
test_case.configure(self.fixture)
221-
return test_case
222-
223-
def _run_single_copy(self):
224-
test_case = self._create_test_case_for_thread(self.logger, thread_id=0)
225-
try:
226-
test_case.run_test()
227-
# If there was an exception, it will be logged in test_case's run_test function.
228-
finally:
229-
self.return_code = test_case.return_code
230-
231-
def _run_multiple_copies(self):
232-
threads = []
233-
test_cases = []
234-
try:
235-
# If there are multiple clients, make a new thread for each client.
236-
for thread_id in xrange(self.num_clients):
237-
logger = self.logger.new_test_thread_logger(self.test_kind, str(thread_id))
238-
test_case = self._create_test_case_for_thread(logger, thread_id)
239-
test_cases.append(test_case)
240-
241-
thread = self.ThreadWithException(target=test_case.run_test)
242-
threads.append(thread)
243-
thread.start()
244-
except:
245-
self.logger.exception("Encountered an error starting threads for jstest %s.",
246-
self.basename())
247-
raise
248-
finally:
249-
for thread in threads:
250-
thread.join()
251-
252-
# Go through each test's return code and store the first nonzero one if it exists.
253-
return_code = 0
254-
for test_case in test_cases:
255-
if test_case.return_code != 0:
256-
return_code = test_case.return_code
257-
break
258-
self.return_code = return_code
259-
260-
for (thread_id, thread) in enumerate(threads):
261-
if thread.exc_info is not None:
262-
if not isinstance(thread.exc_info[1], self.failureException):
263-
self.logger.error(
264-
"Encountered an error inside thread %d running jstest %s.",
265-
thread_id, self.basename(),
266-
exc_info=thread.exc_info)
267-
raise thread.exc_info
268-
269-
def run_test(self):
270-
if self.num_clients == 1:
271-
self._run_single_copy()
272-
else:
273-
self._run_multiple_copies()
179+
return core.programs.mongo_shell_program(
180+
logger,
181+
executable=self.shell_executable,
182+
filename=self.js_filename,
183+
connection_string=self.fixture.get_driver_connection_url(),
184+
**shell_options)
185+
186+
def _run_test_in_thread(self, thread_id):
187+
# Make a logger for each thread. When this method gets called self.logger has been
188+
# overridden with a TestLogger instance by the TestReport in the startTest() method.
189+
logger = self.logger.new_test_thread_logger(self.test_kind, str(thread_id))
190+
shell = self._make_process(logger, thread_id)
191+
self._execute(shell)

0 commit comments

Comments
 (0)