Merge lp:~jml/launchpad/subunit-by-default into lp:launchpad

Proposed by Jonathan Lange
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~jml/launchpad/subunit-by-default
Merge into: lp:launchpad
Diff against target: 461 lines (+146/-141)
3 files modified
lib/devscripts/ec2test/builtins.py (+24/-21)
lib/devscripts/ec2test/ec2test-remote.py (+120/-104)
lib/devscripts/ec2test/testrunner.py (+2/-16)
To merge this branch: bzr merge lp:~jml/launchpad/subunit-by-default
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+18449@code.launchpad.net

Commit message

Generate subunit output on ec2 test, leaving nicer summary output and more analyzable test failures. Make --headless the default for ec2 test.

To post a comment you must log in.
Revision history for this message
Jonathan Lange (jml) wrote :

= Purpose =

This branch is intended to make debugging of test failures on EC2 easier by providing subunit output and by providing better error messages.

It also changes the default behaviour of the 'ec2 test' command to run in headless mode, deletes the '--headless' option and creates a new '--attached' option that is the inverse of the old '--headless' option.

= Implementation =

Most of the command changes are fairly boring. The interesting changes are in ec2test-remote and testrunner.py

In ec2test-remote, I deleted the JS checker, which is no longer used. I also removed the regex-based mechanic for producing output summary.

Instead, the full output is produced via subunit and interpreted using a component from subunit that drives a Python standard library TestResult object. I made my own TestResult object that prints out errors and failures as they occur.

I also rejiggered the subprocess code to have less nesting. I think this has made the behaviour clearer.

Finally, the subunit output is attached to the failure email. The attachment should have the correct mime type and a useful filename.

= Testing =

I've done several test runs through ec2 at various stages of development, including the final stage.

Revision history for this message
Jonathan Lange (jml) wrote :

I forgot to mention. There is an evil hack that makes sure that subunit is available to ec2test-remote.py. I'd love to be able to get rid of it, but I don't know how.

Revision history for this message
Gavin Panella (allenap) wrote :
Download full text (7.1 KiB)

Looks good. I have a couple of questions/suggestions. I'm going to
borrow SummaryResult if that's okay, for my very-long-in-the-tooth-
but-still-going parallelization branch.

> === modified file 'lib/devscripts/ec2test/ec2test-remote.py'
> --- lib/devscripts/ec2test/ec2test-remote.py 2009-10-09 15:04:24 +0000
> +++ lib/devscripts/ec2test/ec2test-remote.py 2010-02-02 13:45:04 +0000
> @@ -106,80 +129,82 @@
> call = self.build_test_command()
>
> try:
> + popen = subprocess.Popen(
> + call, bufsize=-1,
> + stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
> + cwd=self.test_dir)
> +
> + self._gather_test_output(popen.stdout, summary_file, out_file)
> +
> + # Grab the testrunner exit status
> + result = popen.wait()
> +
> + if self.pqm_message is not None:
> + subject = self.pqm_message.get('Subject')
> + if result:
> + # failure
> + summary_file.write(
> + '\n\n**NOT** submitted to PQM:\n%s\n' % (subject,))
> + else:
> + # success
> + conn = bzrlib.smtp_connection.SMTPConnection(config)
> + conn.send_email(self.pqm_message)
> + summary_file.write(
> + '\n\nSUBMITTED TO PQM:\n%s\n' % (subject,))
> + except:
> + summary_file.write('\n\nERROR IN TESTRUNNER\n\n')
> + traceback.print_exc(file=summary_file)
> + result = 1
> + raise
> + finally:
> + # It probably isn't safe to close the log files ourselves,
> + # since someone else might try to write to them later.
> try:
> - try:
> - popen = subprocess.Popen(
> - call, bufsize=-1,
> - stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
> - cwd=self.test_dir)
> -
> - self._gather_test_output(popen, summary_file, out_file)
> -
> - # Grab the testrunner exit status
> - result = popen.wait()
> -
> - if self.pqm_message is not None:
> - subject = self.pqm_message.get('Subject')
> - if result:
> - # failure
> - summary_file.write(
> - '\n\n**NOT** submitted to PQM:\n%s\n' %
> - (subject,))
> - else:
> - # success
> - conn = bzrlib.smtp_connection.SMTPConnection(
> - config)
> - conn.send_email(self.pqm_message)
> - summary_file.write('\n\nSUBMITTED TO PQM:\n%s\n' %
> - (subject,))
> - except:
> - summary_file.write('\n\nERROR IN TESTRUNNER\n\n')
> - traceback.print_exc(file=summary_file)
> - ...

Read more...

review: Approve
Revision history for this message
Gavin Panella (allenap) wrote :

Maybe it's worth adding the subunit PPA <https://launchpad.net/~subunit/+archive/ppa> to the EC2 host and installing from there, instead of doing the sys.path hack? It's not clear that it's a lot cleaner though.

Revision history for this message
Jonathan Lange (jml) wrote :
Download full text (7.6 KiB)

On Tue, Feb 2, 2010 at 2:01 PM, Gavin Panella
<email address hidden> wrote:
> Review: Approve
> Looks good. I have a couple of questions/suggestions. I'm going to
> borrow SummaryResult if that's okay, for my very-long-in-the-tooth-
> but-still-going parallelization branch.
>

It's fine. It'll be in trunk, of course, by the time you get there :)

>
>> === modified file 'lib/devscripts/ec2test/ec2test-remote.py'
>> --- lib/devscripts/ec2test/ec2test-remote.py  2009-10-09 15:04:24 +0000
>> +++ lib/devscripts/ec2test/ec2test-remote.py  2010-02-02 13:45:04 +0000
>> @@ -106,80 +129,82 @@
>>          call = self.build_test_command()
>>
>>          try:
>> +            popen = subprocess.Popen(
>> +                call, bufsize=-1,
>> +                stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
>> +                cwd=self.test_dir)
>> +
>> +            self._gather_test_output(popen.stdout, summary_file, out_file)
>> +
>> +            # Grab the testrunner exit status
>> +            result = popen.wait()
>> +
>> +            if self.pqm_message is not None:
>> +                subject = self.pqm_message.get('Subject')
>> +                if result:
>> +                    # failure
>> +                    summary_file.write(
>> +                        '\n\n**NOT** submitted to PQM:\n%s\n' % (subject,))
>> +                else:
>> +                    # success
>> +                    conn = bzrlib.smtp_connection.SMTPConnection(config)
>> +                    conn.send_email(self.pqm_message)
>> +                    summary_file.write(
>> +                        '\n\nSUBMITTED TO PQM:\n%s\n' % (subject,))
>> +        except:
>> +            summary_file.write('\n\nERROR IN TESTRUNNER\n\n')
>> +            traceback.print_exc(file=summary_file)
>> +            result = 1
>> +            raise
>> +        finally:
>> +            # It probably isn't safe to close the log files ourselves,
>> +            # since someone else might try to write to them later.
>>              try:
>> -                try:
>> -                    popen = subprocess.Popen(
>> -                        call, bufsize=-1,
>> -                        stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
>> -                        cwd=self.test_dir)
>> -
>> -                    self._gather_test_output(popen, summary_file, out_file)
>> -
>> -                    # Grab the testrunner exit status
>> -                    result = popen.wait()
>> -
>> -                    if self.pqm_message is not None:
>> -                        subject = self.pqm_message.get('Subject')
>> -                        if result:
>> -                            # failure
>> -                            summary_file.write(
>> -                                '\n\n**NOT** submitted to PQM:\n%s\n' %
>> -                                (subject,))
>> -                        else:
>> -                            # success
>> -                            conn = bzrlib.smtp_connection.SMTPConnection(
>> -                                config)
>> -                            conn.send_email(self.pqm_message)
>> -                            summary_file.write('\n\nSUBMITTED TO PQM:\n%s\n' %
>...

Read more...

Revision history for this message
Gavin Panella (allenap) wrote :

> > An alternative for all the logs is to open them line buffered,
> > assuming that works as advertised.
>
> How do you do that?
>
> jml

For stdout I think you can open a new file object with line buffering
enabled (untested):

  my_stdout = os.fdopen(sys.stdout.fileno(), 'w', 1)

For regular files, it's similar:

  fd = open('fred.txt', 'w', 1)

Revision history for this message
Jonathan Lange (jml) wrote :

Gavin,

Thanks for the reviews & for the line buffering tip.

I don't think the output of this branch is really where I want it to be for landing, and I don't even think it's an incremental improvement. Also, with further integration testing, I've noticed odd behaviours (although these may be the xvfb bugs people keep mentioning).

In any case, it's not ready to land yet.

jml

Revision history for this message
Jonathan Lange (jml) wrote :

Hello,

Now that the new zope.testing with improved subunit support has landed, I feel comfortable and confident that this branch is ready to land also.

Leaving it up for review, since there has been some change since it was last reviewed.

jml

Revision history for this message
Gavin Panella (allenap) wrote :

Revs 10207, 10208, 10210 and 10211 all look good.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/devscripts/ec2test/builtins.py'
2--- lib/devscripts/ec2test/builtins.py 2010-03-18 10:38:14 +0000
3+++ lib/devscripts/ec2test/builtins.py 2010-04-14 13:31:40 +0000
4@@ -109,6 +109,12 @@
5 'and/or of this script.'))
6
7
8+attached_option = Option(
9+ 'attached',
10+ help=("Remain attached, i.e. do not go headless. Implied by --postmortem "
11+ "and --file."))
12+
13+
14 def filename_type(filename):
15 """An option validator for filenames.
16
17@@ -182,6 +188,10 @@
18 return branches, test_branch
19
20
21+
22+DEFAULT_TEST_OPTIONS = '--subunit -vvv'
23+
24+
25 class cmd_test(EC2Command):
26 """Run the test suite in ec2."""
27
28@@ -240,11 +250,7 @@
29 'consulted; for remote branches "Launchpad PQM '
30 '<launchpad@pqm.canonical.com>" is used by default.')),
31 postmortem_option,
32- Option(
33- 'headless',
34- help=('After building the instance and test, run the remote '
35- 'tests headless. Cannot be used with postmortem '
36- 'or file.')),
37+ attached_option,
38 debug_option,
39 Option(
40 'open-browser',
41@@ -256,10 +262,10 @@
42
43 def run(self, test_branch=None, branch=None, trunk=False, machine=None,
44 instance_type=DEFAULT_INSTANCE_TYPE,
45- file=None, email=None, test_options='-vv', noemail=False,
46- submit_pqm_message=None, pqm_public_location=None,
47+ file=None, email=None, test_options=DEFAULT_TEST_OPTIONS,
48+ noemail=False, submit_pqm_message=None, pqm_public_location=None,
49 pqm_submit_location=None, pqm_email=None, postmortem=False,
50- headless=False, debug=False, open_browser=False,
51+ attached=False, debug=False, open_browser=False,
52 include_download_cache_changes=False):
53 if debug:
54 pdb.set_trace()
55@@ -267,10 +273,8 @@
56 branch = []
57 branches, test_branch = _get_branches_and_test_branch(
58 trunk, branch, test_branch)
59- if ((postmortem or file) and headless):
60- raise BzrCommandError(
61- 'Headless mode currently does not support postmortem or file '
62- ' options.')
63+ if (postmortem or file):
64+ attached = True
65 if noemail:
66 if email:
67 raise BzrCommandError(
68@@ -279,12 +283,13 @@
69 if email == []:
70 email = True
71
72- if headless and not (email or submit_pqm_message):
73+ if not attached and not (email or submit_pqm_message):
74 raise BzrCommandError(
75 'You have specified no way to get the results '
76 'of your headless test run.')
77
78- if test_options != '-vv' and submit_pqm_message is not None:
79+ if (test_options != DEFAULT_TEST_OPTIONS
80+ and submit_pqm_message is not None):
81 raise BzrCommandError(
82 "Submitting to PQM with non-default test options isn't "
83 "supported")
84@@ -295,7 +300,7 @@
85
86 runner = EC2TestRunner(
87 test_branch, email=email, file=file,
88- test_options=test_options, headless=headless,
89+ test_options=test_options, headless=(not attached),
90 branches=branches, pqm_message=submit_pqm_message,
91 pqm_public_location=pqm_public_location,
92 pqm_submit_location=pqm_submit_location,
93@@ -304,7 +309,7 @@
94 instance=instance, launchpad_login=instance._launchpad_login,
95 timeout=480)
96
97- instance.set_up_and_run(postmortem, not headless, runner.run_tests)
98+ instance.set_up_and_run(postmortem, attached, runner.run_tests)
99
100
101 class cmd_land(EC2Command):
102@@ -325,9 +330,7 @@
103 Option(
104 'force',
105 help="Land the branch even if the proposal is not approved."),
106- Option(
107- 'attached',
108- help="Remain attached, i.e. do not go headless."),
109+ attached_option,
110 ]
111
112 takes_args = ['merge_proposal?']
113@@ -337,8 +340,8 @@
114 """Return the command that would need to be run to submit with ec2."""
115 ec2_path = os.path.join(get_launchpad_root(), 'utilities', 'ec2')
116 command = [ec2_path, 'test']
117- if not attached:
118- command.extend(['--headless'])
119+ if attached:
120+ command.extend(['--attached'])
121 command.extend(['--email=%s' % email for email in emails])
122 # 'ec2 test' has a bug where you cannot pass full URLs to branches to
123 # the -b option. It has special logic for 'launchpad' branches, so we
124
125=== modified file 'lib/devscripts/ec2test/ec2test-remote.py'
126--- lib/devscripts/ec2test/ec2test-remote.py 2010-03-18 11:21:40 +0000
127+++ lib/devscripts/ec2test/ec2test-remote.py 2010-04-14 13:31:40 +0000
128@@ -10,12 +10,12 @@
129 import optparse
130 import os
131 import pickle
132-import re
133 import subprocess
134 import sys
135 import textwrap
136 import time
137 import traceback
138+import unittest
139
140 from xml.sax.saxutils import escape
141
142@@ -26,6 +26,66 @@
143 import bzrlib.smtp_connection
144 import bzrlib.workingtree
145
146+import subunit
147+
148+
149+class SummaryResult(unittest.TestResult):
150+ """Test result object used to generate the summary."""
151+
152+ double_line = '=' * 70 + '\n'
153+ single_line = '-' * 70 + '\n'
154+
155+ def __init__(self, output_stream):
156+ super(SummaryResult, self).__init__()
157+ self.stream = output_stream
158+
159+ def printError(self, flavor, test, error):
160+ """Print an error to the output stream."""
161+ self.stream.write(self.double_line)
162+ self.stream.write('%s: %s\n' % (flavor, test))
163+ self.stream.write(self.single_line)
164+ self.stream.write('%s\n' % (error,))
165+ self.stream.flush()
166+
167+ def addError(self, test, error):
168+ super(SummaryResult, self).addError(test, error)
169+ self.printError('ERROR', test, self._exc_info_to_string(error, test))
170+
171+ def addFailure(self, test, error):
172+ super(SummaryResult, self).addFailure(test, error)
173+ self.printError(
174+ 'FAILURE', test, self._exc_info_to_string(error, test))
175+
176+
177+class FlagFallStream:
178+ """Wrapper around a stream that only starts forwarding after a flagfall.
179+ """
180+
181+ def __init__(self, stream, flag):
182+ """Construct a `FlagFallStream` that wraps 'stream'.
183+
184+ :param stream: A stream, a file-like object.
185+ :param flag: A string that needs to be written to this stream before
186+ we start forwarding the output.
187+ """
188+ self._stream = stream
189+ self._flag = flag
190+ self._flag_fallen = False
191+
192+ def write(self, bytes):
193+ if self._flag_fallen:
194+ self._stream.write(bytes)
195+ else:
196+ index = bytes.find(self._flag)
197+ if index == -1:
198+ return
199+ else:
200+ self._stream.write(bytes[index:])
201+ self._flag_fallen = True
202+
203+ def flush(self):
204+ self._stream.flush()
205+
206
207 class BaseTestRunner:
208
209@@ -35,10 +95,6 @@
210 self.pqm_message = pqm_message
211 self.public_branch = public_branch
212 self.public_branch_revno = public_branch_revno
213-
214- # Set up the testrunner options.
215- if test_options is None:
216- test_options = '-vv'
217 self.test_options = test_options
218
219 # Configure paths.
220@@ -106,80 +162,75 @@
221 call = self.build_test_command()
222
223 try:
224+ popen = subprocess.Popen(
225+ call, bufsize=-1,
226+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
227+ cwd=self.test_dir)
228+
229+ self._gather_test_output(popen.stdout, summary_file, out_file)
230+
231+ # Grab the testrunner exit status
232+ result = popen.wait()
233+
234+ if self.pqm_message is not None:
235+ subject = self.pqm_message.get('Subject')
236+ if result:
237+ # failure
238+ summary_file.write(
239+ '\n\n**NOT** submitted to PQM:\n%s\n' % (subject,))
240+ else:
241+ # success
242+ conn = bzrlib.smtp_connection.SMTPConnection(config)
243+ conn.send_email(self.pqm_message)
244+ summary_file.write(
245+ '\n\nSUBMITTED TO PQM:\n%s\n' % (subject,))
246+ except:
247+ summary_file.write('\n\nERROR IN TESTRUNNER\n\n')
248+ traceback.print_exc(file=summary_file)
249+ result = 1
250+ raise
251+ finally:
252+ # It probably isn't safe to close the log files ourselves,
253+ # since someone else might try to write to them later.
254 try:
255- try:
256- popen = subprocess.Popen(
257- call, bufsize=-1,
258- stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
259- cwd=self.test_dir)
260-
261- self._gather_test_output(popen, summary_file, out_file)
262-
263- # Grab the testrunner exit status
264- result = popen.wait()
265-
266- if self.pqm_message is not None:
267- subject = self.pqm_message.get('Subject')
268- if result:
269- # failure
270- summary_file.write(
271- '\n\n**NOT** submitted to PQM:\n%s\n' %
272- (subject,))
273- else:
274- # success
275- conn = bzrlib.smtp_connection.SMTPConnection(
276- config)
277- conn.send_email(self.pqm_message)
278- summary_file.write('\n\nSUBMITTED TO PQM:\n%s\n' %
279- (subject,))
280- except:
281- summary_file.write('\n\nERROR IN TESTRUNNER\n\n')
282- traceback.print_exc(file=summary_file)
283- result = 1
284- raise
285- finally:
286- # It probably isn't safe to close the log files ourselves,
287- # since someone else might try to write to them later.
288 summary_file.close()
289 if self.email is not None:
290 subject = 'Test results: %s' % (
291 result and 'FAILURE' or 'SUCCESS')
292 summary_file = open(self.logger.summary_filename, 'r')
293+ out_file = open(self.logger.out_filename, 'r')
294 bzrlib.email_message.EmailMessage.send(
295 config, config.username(), self.email,
296- subject, summary_file.read())
297+ subject, summary_file.read(),
298+ attachment=out_file.read(),
299+ attachment_filename='%s.log' % self.get_nick(),
300+ attachment_mime_subtype='subunit')
301 summary_file.close()
302- finally:
303- # we do this at the end because this is a trigger to ec2test.py
304- # back at home that it is OK to kill the process and take control
305- # itself, if it wants to.
306- out_file.close()
307- self.logger.close_logs()
308-
309- def _gather_test_output(self, test_process, summary_file, out_file):
310+ finally:
311+ # we do this at the end because this is a trigger to
312+ # ec2test.py back at home that it is OK to kill the process
313+ # and take control itself, if it wants to.
314+ out_file.close()
315+ self.logger.close_logs()
316+
317+ def get_nick(self):
318+ """Return the nick name of the branch that we are testing."""
319+ return self.public_branch.strip('/').split('/')[-1]
320+
321+ def _gather_test_output(self, input_stream, summary_file, out_file):
322 """Write the testrunner output to the logs."""
323 # Only write to stdout if we are running as the foreground process.
324 echo_to_stdout = not self.daemonized
325-
326- last_line = ''
327- while 1:
328- data = test_process.stdout.read(256)
329- if data:
330- out_file.write(data)
331- out_file.flush()
332- if echo_to_stdout:
333- sys.stdout.write(data)
334- sys.stdout.flush()
335- lines = data.split('\n')
336- lines[0] = last_line + lines[0]
337- last_line = lines.pop()
338- for line in lines:
339- if not self.ignore_line(line):
340- summary_file.write(line + '\n')
341- summary_file.flush()
342- else:
343- summary_file.write(last_line)
344- break
345+ result = SummaryResult(summary_file)
346+ subunit_server = subunit.TestProtocolServer(
347+ result, FlagFallStream(summary_file, 'Running tests.'))
348+ for line in input_stream:
349+ subunit_server.lineReceived(line)
350+ out_file.write(line)
351+ out_file.flush()
352+ if echo_to_stdout:
353+ sys.stdout.write(line)
354+ sys.stdout.flush()
355
356
357 class TestOnMergeRunner(BaseTestRunner):
358@@ -190,31 +241,6 @@
359 command = ['make', 'check', 'VERBOSITY=' + self.test_options]
360 return command
361
362- # Used to filter lines in the summary log. See
363- # `BaseTestRunner.ignore_line()`.
364- ignore_line = re.compile(
365- r'( [\w\.\/\-]+( ?\([\w\.\/\-]+\))?|'
366- r'\s*Running.*|'
367- r'\d{4}\-\d{2}\-\d{2} \d{2}\:\d{2}\:\d{2} INFO.+|'
368- r'\s*Set up .+|'
369- r'\s*Tear down .*|'
370- r' Ran \d+ tests with .+)$').match
371-
372-
373-class JSCheckTestRunner(BaseTestRunner):
374- """Executes the Launchpad JavaScript integration test suite."""
375-
376- def build_test_command(self):
377- """See BaseTestRunner.build_test_command()."""
378- # We use the xvfb server's convenience script, xvfb-run, to
379- # automagically set the display, start the command, shut down the
380- # display, and return the exit code. (See the xvfb-run man page for
381- # details.)
382- return [
383- 'xvfb-run',
384- '-s', '-screen 0 1024x768x24',
385- 'make', 'jscheck']
386-
387
388 class WebTestLogger:
389 """Logs test output to disk and a simple web page."""
390@@ -428,9 +454,6 @@
391 '--public-branch-revno', dest='public_branch_revno',
392 type="int", default=None,
393 help=('The revision number of the public branch being tested.'))
394- parser.add_option(
395- '--jscheck', dest='jscheck', default=False, action='store_true',
396- help=('Run the JavaScript integration test suite.'))
397
398 options, args = parser.parse_args()
399
400@@ -442,19 +465,12 @@
401 else:
402 pqm_message = None
403
404- if options.jscheck:
405- runner_type = JSCheckTestRunner
406- else:
407- # Use the default testrunner.
408- runner_type = TestOnMergeRunner
409-
410- runner = runner_type(
411+ runner = TestOnMergeRunner(
412 options.email,
413 pqm_message,
414 options.public_branch,
415 options.public_branch_revno,
416- ' '.join(args)
417- )
418+ ' '.join(args))
419
420 try:
421 try:
422
423=== modified file 'lib/devscripts/ec2test/testrunner.py'
424--- lib/devscripts/ec2test/testrunner.py 2010-03-31 20:52:55 +0000
425+++ lib/devscripts/ec2test/testrunner.py 2010-04-14 13:31:40 +0000
426@@ -109,7 +109,7 @@
427 message = image = None
428 _running = False
429
430- def __init__(self, branch, email=False, file=None, test_options='-vv',
431+ def __init__(self, branch, email=False, file=None, test_options=None,
432 headless=False, branches=(),
433 pqm_message=None, pqm_public_location=None,
434 pqm_submit_location=None,
435@@ -483,7 +483,7 @@
436 cmd.append('--public-branch-revno=%d' % branch_revno)
437
438 # Add any additional options for ec2test-remote.py
439- cmd.extend(self.get_remote_test_options())
440+ cmd.extend(['--', self.test_options])
441 self.log(
442 'Running tests... (output is available on '
443 'http://%s/)\n' % self._instance.hostname)
444@@ -519,17 +519,3 @@
445 'Writing abridged test results to %s.\n' % self.file)
446 user_connection.sftp.get('/var/www/summary.log', self.file)
447 user_connection.close()
448-
449- def get_remote_test_options(self):
450- """Return the test command that will be passed to ec2test-remote.py.
451-
452- Returns a tuple of command-line options and switches.
453- """
454- if '--jscheck' in self.test_options:
455- # We want to run the JavaScript test suite.
456- return ('--jscheck',)
457- else:
458- # Run the normal testsuite with our Zope testrunner options.
459- # ec2test-remote.py wants the extra options to be after a double-
460- # dash.
461- return ('--', self.test_options)