Skip to content

Commit b32a930

Browse files
Explicitly hide pytest output during discovery. (microsoft#4816)
Also: pytest CLI args are now only after "--".
1 parent b47c74e commit b32a930

7 files changed

Lines changed: 151 additions & 30 deletions

File tree

‎pythonFiles/normalizeForInterpreter.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
14
import ast
25
import io
36
import operator
@@ -26,6 +29,8 @@ def _tokenize(source):
2629
"""Tokenize Python source code."""
2730
# Using an undocumented API as the documented one in Python 2.7 does not work as needed
2831
# cross-version.
32+
if sys.version_info < (3,) and isinstance(source, str):
33+
source = source.decode()
2934
return tokenize.generate_tokens(io.StringIO(source).readline)
3035

3136

‎pythonFiles/testing_tools/adapter/__main__.py‎

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,31 +40,32 @@ def parse_args(
4040
# Add "run" and "debug" subcommands when ready.
4141
for cmdname in ['discover']:
4242
sub = cmdsubs.add_parser(cmdname)
43-
if cmdname == 'discover':
44-
sub.add_argument('--simple', action='store_true')
45-
sub.add_argument('--show-pytest', action='store_true')
4643
subsubs = sub.add_subparsers(dest='tool')
4744
for toolname in sorted(TOOLS):
4845
try:
4946
add_subparser = TOOLS[toolname]['_add_subparser']
5047
except KeyError:
5148
continue
52-
add_subparser(cmdname, toolname, subsubs)
49+
subsub = add_subparser(cmdname, toolname, subsubs)
50+
if cmdname == 'discover':
51+
subsub.add_argument('--simple', action='store_true')
52+
subsub.add_argument('--no-hide-stdio', dest='hidestdio',
53+
action='store_false')
54+
subsub.add_argument('--pretty', action='store_true')
5355

5456
# Parse the args!
55-
args, toolargs = parser.parse_known_args(argv)
57+
if '--' in argv:
58+
seppos = argv.index('--')
59+
toolargs = argv[seppos + 1:]
60+
argv = argv[:seppos]
61+
else:
62+
toolargs = []
63+
args = parser.parse_args(argv)
5664
ns = vars(args)
5765

5866
cmd = ns.pop('cmd')
5967
if not cmd:
6068
parser.error('missing command')
61-
if cmd == 'discover':
62-
if '--simple' in toolargs:
63-
toolargs.remove('--simple')
64-
ns['simple'] = True
65-
if '--show-pytest' in toolargs:
66-
toolargs.remove('--show-pytest')
67-
ns['show_pytest'] = True
6869

6970
tool = ns.pop('tool')
7071
if not tool:
@@ -88,7 +89,6 @@ def main(toolname, cmdname, subargs, toolargs,
8889

8990
parents, result = run(toolargs, **subargs)
9091
report_result(result, parents,
91-
debug=('-v' in toolargs or '--verbose' in toolargs),
9292
**subargs
9393
)
9494

‎pythonFiles/testing_tools/adapter/pytest.py‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import pytest
99

10+
from . import util
1011
from .errors import UnsupportedCommandError
1112
from .info import TestInfo, TestPath, ParentInfo
1213

@@ -22,14 +23,17 @@ def add_cli_subparser(cmd, name, parent):
2223
return parser
2324

2425

25-
def discover(pytestargs=None, show_pytest=False,
26-
_pytest_main=pytest.main, _plugin=None, **kwargs):
26+
def discover(pytestargs=None, hidestdio=False,
27+
_pytest_main=pytest.main, _plugin=None, **_ignored):
2728
"""Return the results of test discovery."""
2829
if _plugin is None:
2930
_plugin = TestCollector()
3031

31-
pytestargs = _adjust_pytest_args(pytestargs, show_pytest=show_pytest)
32-
ec = _pytest_main(pytestargs, [_plugin])
32+
pytestargs = _adjust_pytest_args(pytestargs)
33+
# We use this helper rather than "-pno:terminal" due to possible
34+
# platform-dependent issues.
35+
with util.hide_stdio() if hidestdio else util.noop_cm():
36+
ec = _pytest_main(pytestargs, [_plugin])
3337
if ec != 0:
3438
raise Exception('pytest discovery failed (exit code {})'.format(ec))
3539
if not _plugin._started:
@@ -45,12 +49,10 @@ def discover(pytestargs=None, show_pytest=False,
4549
)
4650

4751

48-
def _adjust_pytest_args(pytestargs, show_pytest):
52+
def _adjust_pytest_args(pytestargs):
4953
pytestargs = list(pytestargs) if pytestargs else []
5054
# Duplicate entries should be okay.
5155
pytestargs.insert(0, '--collect-only')
52-
if not show_pytest:
53-
pytestargs.insert(0, '-pno:terminal')
5456
# TODO: pull in code from:
5557
# src/client/unittests/pytest/services/discoveryService.ts
5658
# src/client/unittests/pytest/services/argsService.ts

‎pythonFiles/testing_tools/adapter/report.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
import json
77

88

9-
def report_discovered(tests, parents, debug=False, simple=False,
10-
_send=print, **kwargs):
9+
def report_discovered(tests, parents, pretty=False, simple=False,
10+
_send=print, **_ignored):
1111
"""Serialize the discovered tests and write to stdout."""
1212
if simple:
1313
data = [{
@@ -62,7 +62,7 @@ def report_discovered(tests, parents, debug=False, simple=False,
6262
} for root in sorted(byroot)]
6363

6464
kwargs = {}
65-
if debug:
65+
if pretty:
6666
# human-formatted
6767
kwargs = dict(
6868
sort_keys=True,
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import contextlib
5+
try:
6+
from io import StringIO
7+
except ImportError:
8+
from StringIO import StringIO # 2.7
9+
import sys
10+
11+
12+
@contextlib.contextmanager
13+
def noop_cm():
14+
yield
15+
16+
17+
@contextlib.contextmanager
18+
def hide_stdio():
19+
"""Swallow stdout and stderr."""
20+
ignored = IgnoredIO()
21+
sys.stdout = ignored
22+
sys.stderr = ignored
23+
try:
24+
yield
25+
finally:
26+
sys.stdout = sys.__stdout__
27+
sys.stderr = sys.__stderr__
28+
29+
30+
class IgnoredIO(StringIO):
31+
"""A noop "file"."""
32+
def write(self, msg):
33+
pass

‎pythonFiles/tests/testing_tools/adapter/test___main__.py‎

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ def test_pytest_default(self):
5555

5656
self.assertEqual(tool, 'pytest')
5757
self.assertEqual(cmd, 'discover')
58-
self.assertEqual(args, {'show_pytest': False, 'simple': False})
58+
self.assertEqual(args, {'pretty': False,
59+
'hidestdio': True,
60+
'simple': False})
5961
self.assertEqual(toolargs, [])
6062

6163
def test_pytest_full(self):
@@ -73,16 +75,33 @@ def test_pytest_full(self):
7375

7476
self.assertEqual(tool, 'pytest')
7577
self.assertEqual(cmd, 'discover')
76-
self.assertEqual(args, {'show_pytest': False, 'simple': False})
78+
self.assertEqual(args, {'pretty': False,
79+
'hidestdio': True,
80+
'simple': False})
7781
self.assertEqual(toolargs, [
78-
'--',
7982
'--strict',
8083
'--ignore', 'spam,ham,eggs',
8184
'--pastebin=xyz',
8285
'--no-cov',
8386
'-d',
8487
])
8588

89+
def test_pytest_opts(self):
90+
tool, cmd, args, toolargs = parse_args([
91+
'discover',
92+
'pytest',
93+
'--simple',
94+
'--no-hide-stdio',
95+
'--pretty',
96+
])
97+
98+
self.assertEqual(tool, 'pytest')
99+
self.assertEqual(cmd, 'discover')
100+
self.assertEqual(args, {'pretty': True,
101+
'hidestdio': False,
102+
'simple': True})
103+
self.assertEqual(toolargs, [])
104+
86105
def test_unsupported_tool(self):
87106
with self.assertRaises(SystemExit):
88107
parse_args(['discover', 'unittest'])
@@ -112,8 +131,7 @@ def test_discover(self):
112131

113132
self.assertEqual(tool.calls, [
114133
('spamspamspam.discover', ([],), {'spam': 'eggs'}),
115-
('reporter.report', (tests, parents), {'debug': False,
116-
'spam': 'eggs'}),
134+
('reporter.report', (tests, parents), {'spam': 'eggs'}),
117135
])
118136

119137
def test_unsupported_tool(self):

‎pythonFiles/tests/testing_tools/adapter/test_pytest.py‎

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
3+
from __future__ import print_function
34

5+
try:
6+
from io import StringIO
7+
except ImportError: # 2.7
8+
from StringIO import StringIO
49
import os
510
import os.path
11+
import sys
612
import unittest
713

814
from ...util import Stub, StubProxy
@@ -194,7 +200,6 @@ def test_unsupported_command(self):
194200
class DiscoverTests(unittest.TestCase):
195201

196202
DEFAULT_ARGS = [
197-
'-pno:terminal',
198203
'--collect-only',
199204
]
200205

@@ -231,6 +236,64 @@ def test_failure(self):
231236
'plugins': [plugin]}),
232237
])
233238

239+
def test_stdio_hidden(self):
240+
pytest_stdout = 'spamspamspamspamspamspamspammityspam'
241+
stub = Stub()
242+
def fake_pytest_main(args, plugins):
243+
stub.add_call('pytest.main', None, {'args': args,
244+
'plugins': plugins})
245+
print(pytest_stdout, end='')
246+
return 0
247+
plugin = StubPlugin(stub)
248+
plugin.discovered = []
249+
buf = StringIO()
250+
251+
sys.stdout = buf
252+
try:
253+
discover([], hidestdio=True,
254+
_pytest_main=fake_pytest_main, _plugin=plugin)
255+
finally:
256+
sys.stdout = sys.__stdout__
257+
captured = buf.getvalue()
258+
259+
self.assertEqual(captured, '')
260+
self.assertEqual(stub.calls, [
261+
('pytest.main', None, {'args': self.DEFAULT_ARGS,
262+
'plugins': [plugin]}),
263+
('discovered.parents', None, None),
264+
('discovered.__len__', None, None),
265+
('discovered.__getitem__', (0,), None),
266+
])
267+
268+
def test_stdio_not_hidden(self):
269+
pytest_stdout = 'spamspamspamspamspamspamspammityspam'
270+
stub = Stub()
271+
def fake_pytest_main(args, plugins):
272+
stub.add_call('pytest.main', None, {'args': args,
273+
'plugins': plugins})
274+
print(pytest_stdout, end='')
275+
return 0
276+
plugin = StubPlugin(stub)
277+
plugin.discovered = []
278+
buf = StringIO()
279+
280+
sys.stdout = buf
281+
try:
282+
discover([], hidestdio=False,
283+
_pytest_main=fake_pytest_main, _plugin=plugin)
284+
finally:
285+
sys.stdout = sys.__stdout__
286+
captured = buf.getvalue()
287+
288+
self.assertEqual(captured, pytest_stdout)
289+
self.assertEqual(stub.calls, [
290+
('pytest.main', None, {'args': self.DEFAULT_ARGS,
291+
'plugins': [plugin]}),
292+
('discovered.parents', None, None),
293+
('discovered.__len__', None, None),
294+
('discovered.__getitem__', (0,), None),
295+
])
296+
234297

235298
class CollectorTests(unittest.TestCase):
236299

@@ -301,7 +364,7 @@ def test_modifyitems(self):
301364
# ignored (pytest-supported)
302365
'parameterize', 'usefixtures', 'filterwarnings',
303366
# ignored (custom)
304-
'timeout',
367+
'timeout',
305368
]],
306369
),
307370
])

0 commit comments

Comments
 (0)