##// END OF EJS Templates
Also run test with Pytest....
Matthias Bussonnier -
Show More
@@ -0,0 +1,69 b''
1 import types
2 import sys
3 import builtins
4 import os
5 import pytest
6 import pathlib
7 import shutil
8
9 from IPython.testing import tools
10
11
12 def get_ipython():
13 from IPython.terminal.interactiveshell import TerminalInteractiveShell
14 if TerminalInteractiveShell._instance:
15 return TerminalInteractiveShell.instance()
16
17 config = tools.default_config()
18 config.TerminalInteractiveShell.simple_prompt = True
19
20 # Create and initialize our test-friendly IPython instance.
21 shell = TerminalInteractiveShell.instance(config=config)
22 return shell
23
24
25 @pytest.fixture(scope='session', autouse=True)
26 def work_path():
27 path = pathlib.Path("./tmp-ipython-pytest-profiledir")
28 os.environ["IPYTHONDIR"] = str(path.absolute())
29 if path.exists():
30 raise ValueError('IPython dir temporary path already exists ! Did previous test run exit successfully ?')
31 path.mkdir()
32 yield
33 shutil.rmtree(str(path.resolve()))
34
35
36 def nopage(strng, start=0, screen_lines=0, pager_cmd=None):
37 if isinstance(strng, dict):
38 strng = strng.get("text/plain", "")
39 print(strng)
40
41
42 def xsys(self, cmd):
43 """Replace the default system call with a capturing one for doctest.
44 """
45 # We use getoutput, but we need to strip it because pexpect captures
46 # the trailing newline differently from commands.getoutput
47 print(self.getoutput(cmd, split=False, depth=1).rstrip(), end="", file=sys.stdout)
48 sys.stdout.flush()
49
50
51 # for things to work correctly we would need this as a session fixture;
52 # unfortunately this will fail on some test that get executed as _collection_
53 # time (before the fixture run), in particular parametrized test that contain
54 # yields. so for now execute at import time.
55 #@pytest.fixture(autouse=True, scope='session')
56 def inject():
57
58 builtins.get_ipython = get_ipython
59 builtins._ip = get_ipython()
60 builtins.ip = get_ipython()
61 builtins.ip.system = types.MethodType(xsys, ip)
62 builtins.ip.builtin_trap.activate()
63 from IPython.core import page
64
65 page.pager_page = nopage
66 # yield
67
68
69 inject()
@@ -0,0 +1,2 b''
1 [pytest]
2 addopts = --duration=10
@@ -1,107 +1,109 b''
1 1 # http://travis-ci.org/#!/ipython/ipython
2 2 language: python
3 3 os: linux
4 4
5 5 addons:
6 6 apt:
7 7 packages:
8 8 - graphviz
9 9
10 10 python:
11 11 - 3.6
12 12 - 3.5
13 13
14 14 sudo: false
15 15
16 16 env:
17 17 global:
18 18 - PATH=$TRAVIS_BUILD_DIR/pandoc:$PATH
19 19
20 20 group: edge
21 21
22 22 before_install:
23 23 - |
24 24 # install Python on macOS
25 25 if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
26 26 env | sort
27 27 if ! which python$TRAVIS_PYTHON_VERSION; then
28 28 HOMEBREW_NO_AUTO_UPDATE=1 brew tap minrk/homebrew-python-frameworks
29 29 HOMEBREW_NO_AUTO_UPDATE=1 brew cask install python-framework-${TRAVIS_PYTHON_VERSION/./}
30 30 fi
31 31 python3 -m pip install virtualenv
32 32 python3 -m virtualenv -p $(which python$TRAVIS_PYTHON_VERSION) ~/travis-env
33 33 source ~/travis-env/bin/activate
34 34 fi
35 35 - python --version
36 36
37 37 install:
38 38 - pip install pip --upgrade
39 39 - pip install setuptools --upgrade
40 40 - pip install -e file://$PWD#egg=ipython[test] --upgrade
41 41 - pip install trio curio
42 - pip install 'pytest<4' matplotlib
42 43 - pip install codecov check-manifest --upgrade
43 44
44 45 script:
45 46 - check-manifest
46 47 - |
47 48 if [[ "$TRAVIS_PYTHON_VERSION" == "nightly" ]]; then
48 49 # on nightly fake parso known the grammar
49 50 cp /home/travis/virtualenv/python3.8-dev/lib/python3.8/site-packages/parso/python/grammar37.txt /home/travis/virtualenv/python3.8-dev/lib/python3.8/site-packages/parso/python/grammar38.txt
50 51 fi
51 52 - cd /tmp && iptest --coverage xml && cd -
53 - pytest IPython
52 54 # On the latest Python (on Linux) only, make sure that the docs build.
53 55 - |
54 56 if [[ "$TRAVIS_PYTHON_VERSION" == "3.7" ]] && [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
55 57 pip install -r docs/requirements.txt
56 58 python tools/fixup_whats_new_pr.py
57 59 make -C docs/ html SPHINXOPTS="-W"
58 60 fi
59 61
60 62 after_success:
61 63 - cp /tmp/ipy_coverage.xml ./
62 64 - cp /tmp/.coverage ./
63 65 - codecov
64 66
65 67 matrix:
66 68 include:
67 69 - python: "3.7"
68 70 dist: xenial
69 71 sudo: true
70 72 - python: "3.8-dev"
71 73 dist: xenial
72 74 sudo: true
73 75 - python: "3.7-dev"
74 76 dist: xenial
75 77 sudo: true
76 78 - python: "nightly"
77 79 dist: xenial
78 80 sudo: true
79 81 - os: osx
80 82 language: generic
81 83 python: 3.6
82 84 env: TRAVIS_PYTHON_VERSION=3.6
83 85 - os: osx
84 86 language: generic
85 87 python: 3.7
86 88 env: TRAVIS_PYTHON_VERSION=3.7
87 89 allow_failures:
88 90 - python: nightly
89 91
90 92 before_deploy:
91 93 - rm -rf dist/
92 94 - python setup.py sdist
93 95 - python setup.py bdist_wheel
94 96
95 97 deploy:
96 98 provider: releases
97 99 api_key:
98 100 secure: Y/Ae9tYs5aoBU8bDjN2YrwGG6tCbezj/h3Lcmtx8HQavSbBgXnhnZVRb2snOKD7auqnqjfT/7QMm4ZyKvaOEgyggGktKqEKYHC8KOZ7yp8I5/UMDtk6j9TnXpSqqBxPiud4MDV76SfRYEQiaDoG4tGGvSfPJ9KcNjKrNvSyyxns=
99 101 file: dist/*
100 102 file_glob: true
101 103 skip_cleanup: true
102 104 on:
103 105 repo: ipython/ipython
104 106 all_branches: true # Backports are released from e.g. 5.x branch
105 107 tags: true
106 108 python: 3.6 # Any version should work, but we only need one
107 109 condition: $TRAVIS_OS_NAME = "linux"
@@ -1,62 +1,65 b''
1 1 from IPython.utils.capture import capture_output
2 2
3 3 import nose.tools as nt
4 4
5 5 def test_alias_lifecycle():
6 6 name = 'test_alias1'
7 7 cmd = 'echo "Hello"'
8 8 am = _ip.alias_manager
9 9 am.clear_aliases()
10 10 am.define_alias(name, cmd)
11 11 assert am.is_alias(name)
12 12 nt.assert_equal(am.retrieve_alias(name), cmd)
13 13 nt.assert_in((name, cmd), am.aliases)
14 14
15 15 # Test running the alias
16 16 orig_system = _ip.system
17 17 result = []
18 18 _ip.system = result.append
19 19 try:
20 20 _ip.run_cell('%{}'.format(name))
21 21 result = [c.strip() for c in result]
22 22 nt.assert_equal(result, [cmd])
23 23 finally:
24 24 _ip.system = orig_system
25 25
26 26 # Test removing the alias
27 27 am.undefine_alias(name)
28 28 assert not am.is_alias(name)
29 29 with nt.assert_raises(ValueError):
30 30 am.retrieve_alias(name)
31 31 nt.assert_not_in((name, cmd), am.aliases)
32 32
33 33
34 34 def test_alias_args_error():
35 35 """Error expanding with wrong number of arguments"""
36 36 _ip.alias_manager.define_alias('parts', 'echo first %s second %s')
37 37 # capture stderr:
38 38 with capture_output() as cap:
39 39 _ip.run_cell('parts 1')
40 40
41 41 nt.assert_equal(cap.stderr.split(':')[0], 'UsageError')
42
42
43 43 def test_alias_args_commented():
44 44 """Check that alias correctly ignores 'commented out' args"""
45 45 _ip.magic('alias commetarg echo this is %%s a commented out arg')
46 46
47 47 with capture_output() as cap:
48 48 _ip.run_cell('commetarg')
49 49
50 nt.assert_equal(cap.stdout, 'this is %s a commented out arg')
50 # strip() is for pytest compat; testing via iptest patch IPython shell
51 # in testin.globalipapp and replace the system call which messed up the
52 # \r\n
53 assert cap.stdout.strip() == 'this is %s a commented out arg'
51 54
52 55 def test_alias_args_commented_nargs():
53 56 """Check that alias correctly counts args, excluding those commented out"""
54 57 am = _ip.alias_manager
55 58 alias_name = 'comargcount'
56 59 cmd = 'echo this is %%s a commented out arg and this is not %s'
57 60
58 61 am.define_alias(alias_name, cmd)
59 62 assert am.is_alias(alias_name)
60 63
61 64 thealias = am.get_alias(alias_name)
62 nt.assert_equal(thealias.nargs, 1) No newline at end of file
65 nt.assert_equal(thealias.nargs, 1)
@@ -1,1172 +1,1170 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Tests for various magic functions.
3 3
4 4 Needs to be run by nose (to make ipython session available).
5 5 """
6 6
7 7 import io
8 8 import os
9 9 import re
10 10 import sys
11 11 import warnings
12 12 from textwrap import dedent
13 13 from unittest import TestCase
14 14 from importlib import invalidate_caches
15 15 from io import StringIO
16 16
17 17 import nose.tools as nt
18 18
19 19 import shlex
20 20
21 21 from IPython import get_ipython
22 22 from IPython.core import magic
23 23 from IPython.core.error import UsageError
24 24 from IPython.core.magic import (Magics, magics_class, line_magic,
25 25 cell_magic,
26 26 register_line_magic, register_cell_magic)
27 27 from IPython.core.magics import execution, script, code, logging, osm
28 28 from IPython.testing import decorators as dec
29 29 from IPython.testing import tools as tt
30 30 from IPython.utils.io import capture_output
31 31 from IPython.utils.tempdir import (TemporaryDirectory,
32 32 TemporaryWorkingDirectory)
33 33 from IPython.utils.process import find_cmd
34 34
35 35
36
37 _ip = get_ipython()
38
39 36 @magic.magics_class
40 37 class DummyMagics(magic.Magics): pass
41 38
42 39 def test_extract_code_ranges():
43 40 instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :"
44 41 expected = [(0, 1),
45 42 (2, 3),
46 43 (4, 6),
47 44 (6, 9),
48 45 (9, 14),
49 46 (16, None),
50 47 (None, 9),
51 48 (9, None),
52 49 (None, 13),
53 50 (None, None)]
54 51 actual = list(code.extract_code_ranges(instr))
55 52 nt.assert_equal(actual, expected)
56 53
57 54 def test_extract_symbols():
58 55 source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n"""
59 56 symbols_args = ["a", "b", "A", "A,b", "A,a", "z"]
60 57 expected = [([], ['a']),
61 58 (["def b():\n return 42\n"], []),
62 59 (["class A: pass\n"], []),
63 60 (["class A: pass\n", "def b():\n return 42\n"], []),
64 61 (["class A: pass\n"], ['a']),
65 62 ([], ['z'])]
66 63 for symbols, exp in zip(symbols_args, expected):
67 64 nt.assert_equal(code.extract_symbols(source, symbols), exp)
68 65
69 66
70 67 def test_extract_symbols_raises_exception_with_non_python_code():
71 68 source = ("=begin A Ruby program :)=end\n"
72 69 "def hello\n"
73 70 "puts 'Hello world'\n"
74 71 "end")
75 72 with nt.assert_raises(SyntaxError):
76 73 code.extract_symbols(source, "hello")
77 74
78 75
79 76 def test_magic_not_found():
80 77 # magic not found raises UsageError
81 78 with nt.assert_raises(UsageError):
82 79 _ip.magic('doesntexist')
83 80
84 81 # ensure result isn't success when a magic isn't found
85 82 result = _ip.run_cell('%doesntexist')
86 83 assert isinstance(result.error_in_exec, UsageError)
87 84
88 85
89 86 def test_cell_magic_not_found():
90 87 # magic not found raises UsageError
91 88 with nt.assert_raises(UsageError):
92 89 _ip.run_cell_magic('doesntexist', 'line', 'cell')
93 90
94 91 # ensure result isn't success when a magic isn't found
95 92 result = _ip.run_cell('%%doesntexist')
96 93 assert isinstance(result.error_in_exec, UsageError)
97 94
98 95
99 96 def test_magic_error_status():
100 97 def fail(shell):
101 98 1/0
102 99 _ip.register_magic_function(fail)
103 100 result = _ip.run_cell('%fail')
104 101 assert isinstance(result.error_in_exec, ZeroDivisionError)
105 102
106 103
107 104 def test_config():
108 105 """ test that config magic does not raise
109 106 can happen if Configurable init is moved too early into
110 107 Magics.__init__ as then a Config object will be registered as a
111 108 magic.
112 109 """
113 110 ## should not raise.
114 111 _ip.magic('config')
115 112
116 113 def test_config_available_configs():
117 114 """ test that config magic prints available configs in unique and
118 115 sorted order. """
119 116 with capture_output() as captured:
120 117 _ip.magic('config')
121 118
122 119 stdout = captured.stdout
123 120 config_classes = stdout.strip().split('\n')[1:]
124 121 nt.assert_list_equal(config_classes, sorted(set(config_classes)))
125 122
126 123 def test_config_print_class():
127 124 """ test that config with a classname prints the class's options. """
128 125 with capture_output() as captured:
129 126 _ip.magic('config TerminalInteractiveShell')
130 127
131 128 stdout = captured.stdout
132 129 if not re.match("TerminalInteractiveShell.* options", stdout.splitlines()[0]):
133 130 print(stdout)
134 131 raise AssertionError("1st line of stdout not like "
135 132 "'TerminalInteractiveShell.* options'")
136 133
137 134 def test_rehashx():
138 135 # clear up everything
139 136 _ip.alias_manager.clear_aliases()
140 137 del _ip.db['syscmdlist']
141 138
142 139 _ip.magic('rehashx')
143 140 # Practically ALL ipython development systems will have more than 10 aliases
144 141
145 142 nt.assert_true(len(_ip.alias_manager.aliases) > 10)
146 143 for name, cmd in _ip.alias_manager.aliases:
147 144 # we must strip dots from alias names
148 145 nt.assert_not_in('.', name)
149 146
150 147 # rehashx must fill up syscmdlist
151 148 scoms = _ip.db['syscmdlist']
152 149 nt.assert_true(len(scoms) > 10)
150
153 151
154 152
155 153 def test_magic_parse_options():
156 154 """Test that we don't mangle paths when parsing magic options."""
157 155 ip = get_ipython()
158 156 path = 'c:\\x'
159 157 m = DummyMagics(ip)
160 158 opts = m.parse_options('-f %s' % path,'f:')[0]
161 159 # argv splitting is os-dependent
162 160 if os.name == 'posix':
163 161 expected = 'c:x'
164 162 else:
165 163 expected = path
166 164 nt.assert_equal(opts['f'], expected)
167 165
168 166 def test_magic_parse_long_options():
169 167 """Magic.parse_options can handle --foo=bar long options"""
170 168 ip = get_ipython()
171 169 m = DummyMagics(ip)
172 170 opts, _ = m.parse_options('--foo --bar=bubble', 'a', 'foo', 'bar=')
173 171 nt.assert_in('foo', opts)
174 172 nt.assert_in('bar', opts)
175 173 nt.assert_equal(opts['bar'], "bubble")
176 174
177 175
178 176 @dec.skip_without('sqlite3')
179 177 def doctest_hist_f():
180 178 """Test %hist -f with temporary filename.
181 179
182 180 In [9]: import tempfile
183 181
184 182 In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-')
185 183
186 184 In [11]: %hist -nl -f $tfile 3
187 185
188 186 In [13]: import os; os.unlink(tfile)
189 187 """
190 188
191 189
192 190 @dec.skip_without('sqlite3')
193 191 def doctest_hist_r():
194 192 """Test %hist -r
195 193
196 194 XXX - This test is not recording the output correctly. For some reason, in
197 195 testing mode the raw history isn't getting populated. No idea why.
198 196 Disabling the output checking for now, though at least we do run it.
199 197
200 198 In [1]: 'hist' in _ip.lsmagic()
201 199 Out[1]: True
202 200
203 201 In [2]: x=1
204 202
205 203 In [3]: %hist -rl 2
206 204 x=1 # random
207 205 %hist -r 2
208 206 """
209 207
210 208
211 209 @dec.skip_without('sqlite3')
212 210 def doctest_hist_op():
213 211 """Test %hist -op
214 212
215 213 In [1]: class b(float):
216 214 ...: pass
217 215 ...:
218 216
219 217 In [2]: class s(object):
220 218 ...: def __str__(self):
221 219 ...: return 's'
222 220 ...:
223 221
224 222 In [3]:
225 223
226 224 In [4]: class r(b):
227 225 ...: def __repr__(self):
228 226 ...: return 'r'
229 227 ...:
230 228
231 229 In [5]: class sr(s,r): pass
232 230 ...:
233 231
234 232 In [6]:
235 233
236 234 In [7]: bb=b()
237 235
238 236 In [8]: ss=s()
239 237
240 238 In [9]: rr=r()
241 239
242 240 In [10]: ssrr=sr()
243 241
244 242 In [11]: 4.5
245 243 Out[11]: 4.5
246 244
247 245 In [12]: str(ss)
248 246 Out[12]: 's'
249 247
250 248 In [13]:
251 249
252 250 In [14]: %hist -op
253 251 >>> class b:
254 252 ... pass
255 253 ...
256 254 >>> class s(b):
257 255 ... def __str__(self):
258 256 ... return 's'
259 257 ...
260 258 >>>
261 259 >>> class r(b):
262 260 ... def __repr__(self):
263 261 ... return 'r'
264 262 ...
265 263 >>> class sr(s,r): pass
266 264 >>>
267 265 >>> bb=b()
268 266 >>> ss=s()
269 267 >>> rr=r()
270 268 >>> ssrr=sr()
271 269 >>> 4.5
272 270 4.5
273 271 >>> str(ss)
274 272 's'
275 273 >>>
276 274 """
277 275
278 276 def test_hist_pof():
279 277 ip = get_ipython()
280 278 ip.run_cell(u"1+2", store_history=True)
281 279 #raise Exception(ip.history_manager.session_number)
282 280 #raise Exception(list(ip.history_manager._get_range_session()))
283 281 with TemporaryDirectory() as td:
284 282 tf = os.path.join(td, 'hist.py')
285 283 ip.run_line_magic('history', '-pof %s' % tf)
286 284 assert os.path.isfile(tf)
287 285
288 286
289 287 @dec.skip_without('sqlite3')
290 288 def test_macro():
291 289 ip = get_ipython()
292 290 ip.history_manager.reset() # Clear any existing history.
293 291 cmds = ["a=1", "def b():\n return a**2", "print(a,b())"]
294 292 for i, cmd in enumerate(cmds, start=1):
295 293 ip.history_manager.store_inputs(i, cmd)
296 294 ip.magic("macro test 1-3")
297 295 nt.assert_equal(ip.user_ns["test"].value, "\n".join(cmds)+"\n")
298 296
299 297 # List macros
300 298 nt.assert_in("test", ip.magic("macro"))
301 299
302 300
303 301 @dec.skip_without('sqlite3')
304 302 def test_macro_run():
305 303 """Test that we can run a multi-line macro successfully."""
306 304 ip = get_ipython()
307 305 ip.history_manager.reset()
308 306 cmds = ["a=10", "a+=1", "print(a)", "%macro test 2-3"]
309 307 for cmd in cmds:
310 308 ip.run_cell(cmd, store_history=True)
311 309 nt.assert_equal(ip.user_ns["test"].value, "a+=1\nprint(a)\n")
312 310 with tt.AssertPrints("12"):
313 311 ip.run_cell("test")
314 312 with tt.AssertPrints("13"):
315 313 ip.run_cell("test")
316 314
317 315
318 316 def test_magic_magic():
319 317 """Test %magic"""
320 318 ip = get_ipython()
321 319 with capture_output() as captured:
322 320 ip.magic("magic")
323 321
324 322 stdout = captured.stdout
325 323 nt.assert_in('%magic', stdout)
326 324 nt.assert_in('IPython', stdout)
327 325 nt.assert_in('Available', stdout)
328 326
329 327
330 328 @dec.skipif_not_numpy
331 329 def test_numpy_reset_array_undec():
332 330 "Test '%reset array' functionality"
333 331 _ip.ex('import numpy as np')
334 332 _ip.ex('a = np.empty(2)')
335 333 nt.assert_in('a', _ip.user_ns)
336 334 _ip.magic('reset -f array')
337 335 nt.assert_not_in('a', _ip.user_ns)
338 336
339 337 def test_reset_out():
340 338 "Test '%reset out' magic"
341 339 _ip.run_cell("parrot = 'dead'", store_history=True)
342 340 # test '%reset -f out', make an Out prompt
343 341 _ip.run_cell("parrot", store_history=True)
344 342 nt.assert_true('dead' in [_ip.user_ns[x] for x in ('_','__','___')])
345 343 _ip.magic('reset -f out')
346 344 nt.assert_false('dead' in [_ip.user_ns[x] for x in ('_','__','___')])
347 345 nt.assert_equal(len(_ip.user_ns['Out']), 0)
348 346
349 347 def test_reset_in():
350 348 "Test '%reset in' magic"
351 349 # test '%reset -f in'
352 350 _ip.run_cell("parrot", store_history=True)
353 351 nt.assert_true('parrot' in [_ip.user_ns[x] for x in ('_i','_ii','_iii')])
354 352 _ip.magic('%reset -f in')
355 353 nt.assert_false('parrot' in [_ip.user_ns[x] for x in ('_i','_ii','_iii')])
356 354 nt.assert_equal(len(set(_ip.user_ns['In'])), 1)
357 355
358 356 def test_reset_dhist():
359 357 "Test '%reset dhist' magic"
360 358 _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing
361 359 _ip.magic('cd ' + os.path.dirname(nt.__file__))
362 360 _ip.magic('cd -')
363 361 nt.assert_true(len(_ip.user_ns['_dh']) > 0)
364 362 _ip.magic('reset -f dhist')
365 363 nt.assert_equal(len(_ip.user_ns['_dh']), 0)
366 364 _ip.run_cell("_dh = [d for d in tmp]") #restore
367 365
368 366 def test_reset_in_length():
369 367 "Test that '%reset in' preserves In[] length"
370 368 _ip.run_cell("print 'foo'")
371 369 _ip.run_cell("reset -f in")
372 370 nt.assert_equal(len(_ip.user_ns['In']), _ip.displayhook.prompt_count+1)
373 371
374 372 def test_tb_syntaxerror():
375 373 """test %tb after a SyntaxError"""
376 374 ip = get_ipython()
377 375 ip.run_cell("for")
378 376
379 377 # trap and validate stdout
380 378 save_stdout = sys.stdout
381 379 try:
382 380 sys.stdout = StringIO()
383 381 ip.run_cell("%tb")
384 382 out = sys.stdout.getvalue()
385 383 finally:
386 384 sys.stdout = save_stdout
387 385 # trim output, and only check the last line
388 386 last_line = out.rstrip().splitlines()[-1].strip()
389 387 nt.assert_equal(last_line, "SyntaxError: invalid syntax")
390 388
391 389
392 390 def test_time():
393 391 ip = get_ipython()
394 392
395 393 with tt.AssertPrints("Wall time: "):
396 394 ip.run_cell("%time None")
397 395
398 396 ip.run_cell("def f(kmjy):\n"
399 397 " %time print (2*kmjy)")
400 398
401 399 with tt.AssertPrints("Wall time: "):
402 400 with tt.AssertPrints("hihi", suppress=False):
403 401 ip.run_cell("f('hi')")
404 402
405 403
406 404 @dec.skip_win32
407 405 def test_time2():
408 406 ip = get_ipython()
409 407
410 408 with tt.AssertPrints("CPU times: user "):
411 409 ip.run_cell("%time None")
412 410
413 411 def test_time3():
414 412 """Erroneous magic function calls, issue gh-3334"""
415 413 ip = get_ipython()
416 414 ip.user_ns.pop('run', None)
417 415
418 416 with tt.AssertNotPrints("not found", channel='stderr'):
419 417 ip.run_cell("%%time\n"
420 418 "run = 0\n"
421 419 "run += 1")
422 420
423 421 def test_multiline_time():
424 422 """Make sure last statement from time return a value."""
425 423 ip = get_ipython()
426 424 ip.user_ns.pop('run', None)
427 425
428 426 ip.run_cell(dedent("""\
429 427 %%time
430 428 a = "ho"
431 429 b = "hey"
432 430 a+b
433 431 """))
434 432 nt.assert_equal(ip.user_ns_hidden['_'], 'hohey')
435 433
436 434 def test_time_local_ns():
437 435 """
438 436 Test that local_ns is actually global_ns when running a cell magic
439 437 """
440 438 ip = get_ipython()
441 439 ip.run_cell("%%time\n"
442 440 "myvar = 1")
443 441 nt.assert_equal(ip.user_ns['myvar'], 1)
444 442 del ip.user_ns['myvar']
445 443
446 444 def test_doctest_mode():
447 445 "Toggle doctest_mode twice, it should be a no-op and run without error"
448 446 _ip.magic('doctest_mode')
449 447 _ip.magic('doctest_mode')
450 448
451 449
452 450 def test_parse_options():
453 451 """Tests for basic options parsing in magics."""
454 452 # These are only the most minimal of tests, more should be added later. At
455 453 # the very least we check that basic text/unicode calls work OK.
456 454 m = DummyMagics(_ip)
457 455 nt.assert_equal(m.parse_options('foo', '')[1], 'foo')
458 456 nt.assert_equal(m.parse_options(u'foo', '')[1], u'foo')
459 457
460 458
461 459 def test_dirops():
462 460 """Test various directory handling operations."""
463 461 # curpath = lambda :os.path.splitdrive(os.getcwd())[1].replace('\\','/')
464 462 curpath = os.getcwd
465 463 startdir = os.getcwd()
466 464 ipdir = os.path.realpath(_ip.ipython_dir)
467 465 try:
468 466 _ip.magic('cd "%s"' % ipdir)
469 467 nt.assert_equal(curpath(), ipdir)
470 468 _ip.magic('cd -')
471 469 nt.assert_equal(curpath(), startdir)
472 470 _ip.magic('pushd "%s"' % ipdir)
473 471 nt.assert_equal(curpath(), ipdir)
474 472 _ip.magic('popd')
475 473 nt.assert_equal(curpath(), startdir)
476 474 finally:
477 475 os.chdir(startdir)
478 476
479 477
480 478 def test_cd_force_quiet():
481 479 """Test OSMagics.cd_force_quiet option"""
482 480 _ip.config.OSMagics.cd_force_quiet = True
483 481 osmagics = osm.OSMagics(shell=_ip)
484 482
485 483 startdir = os.getcwd()
486 484 ipdir = os.path.realpath(_ip.ipython_dir)
487 485
488 486 try:
489 487 with tt.AssertNotPrints(ipdir):
490 488 osmagics.cd('"%s"' % ipdir)
491 489 with tt.AssertNotPrints(startdir):
492 490 osmagics.cd('-')
493 491 finally:
494 492 os.chdir(startdir)
495 493
496 494
497 495 def test_xmode():
498 496 # Calling xmode three times should be a no-op
499 497 xmode = _ip.InteractiveTB.mode
500 498 for i in range(4):
501 499 _ip.magic("xmode")
502 500 nt.assert_equal(_ip.InteractiveTB.mode, xmode)
503 501
504 502 def test_reset_hard():
505 503 monitor = []
506 504 class A(object):
507 505 def __del__(self):
508 506 monitor.append(1)
509 507 def __repr__(self):
510 508 return "<A instance>"
511 509
512 510 _ip.user_ns["a"] = A()
513 511 _ip.run_cell("a")
514 512
515 513 nt.assert_equal(monitor, [])
516 514 _ip.magic("reset -f")
517 515 nt.assert_equal(monitor, [1])
518 516
519 517 class TestXdel(tt.TempFileMixin):
520 518 def test_xdel(self):
521 519 """Test that references from %run are cleared by xdel."""
522 520 src = ("class A(object):\n"
523 521 " monitor = []\n"
524 522 " def __del__(self):\n"
525 523 " self.monitor.append(1)\n"
526 524 "a = A()\n")
527 525 self.mktmp(src)
528 526 # %run creates some hidden references...
529 527 _ip.magic("run %s" % self.fname)
530 528 # ... as does the displayhook.
531 529 _ip.run_cell("a")
532 530
533 531 monitor = _ip.user_ns["A"].monitor
534 532 nt.assert_equal(monitor, [])
535 533
536 534 _ip.magic("xdel a")
537 535
538 536 # Check that a's __del__ method has been called.
539 537 nt.assert_equal(monitor, [1])
540 538
541 539 def doctest_who():
542 540 """doctest for %who
543 541
544 542 In [1]: %reset -f
545 543
546 544 In [2]: alpha = 123
547 545
548 546 In [3]: beta = 'beta'
549 547
550 548 In [4]: %who int
551 549 alpha
552 550
553 551 In [5]: %who str
554 552 beta
555 553
556 554 In [6]: %whos
557 555 Variable Type Data/Info
558 556 ----------------------------
559 557 alpha int 123
560 558 beta str beta
561 559
562 560 In [7]: %who_ls
563 561 Out[7]: ['alpha', 'beta']
564 562 """
565 563
566 564 def test_whos():
567 565 """Check that whos is protected against objects where repr() fails."""
568 566 class A(object):
569 567 def __repr__(self):
570 568 raise Exception()
571 569 _ip.user_ns['a'] = A()
572 570 _ip.magic("whos")
573 571
574 572 def doctest_precision():
575 573 """doctest for %precision
576 574
577 575 In [1]: f = get_ipython().display_formatter.formatters['text/plain']
578 576
579 577 In [2]: %precision 5
580 578 Out[2]: '%.5f'
581 579
582 580 In [3]: f.float_format
583 581 Out[3]: '%.5f'
584 582
585 583 In [4]: %precision %e
586 584 Out[4]: '%e'
587 585
588 586 In [5]: f(3.1415927)
589 587 Out[5]: '3.141593e+00'
590 588 """
591 589
592 590 def test_psearch():
593 591 with tt.AssertPrints("dict.fromkeys"):
594 592 _ip.run_cell("dict.fr*?")
595 593
596 594 def test_timeit_shlex():
597 595 """test shlex issues with timeit (#1109)"""
598 596 _ip.ex("def f(*a,**kw): pass")
599 597 _ip.magic('timeit -n1 "this is a bug".count(" ")')
600 598 _ip.magic('timeit -r1 -n1 f(" ", 1)')
601 599 _ip.magic('timeit -r1 -n1 f(" ", 1, " ", 2, " ")')
602 600 _ip.magic('timeit -r1 -n1 ("a " + "b")')
603 601 _ip.magic('timeit -r1 -n1 f("a " + "b")')
604 602 _ip.magic('timeit -r1 -n1 f("a " + "b ")')
605 603
606 604
607 605 def test_timeit_special_syntax():
608 606 "Test %%timeit with IPython special syntax"
609 607 @register_line_magic
610 608 def lmagic(line):
611 609 ip = get_ipython()
612 610 ip.user_ns['lmagic_out'] = line
613 611
614 612 # line mode test
615 613 _ip.run_line_magic('timeit', '-n1 -r1 %lmagic my line')
616 614 nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line')
617 615 # cell mode test
618 616 _ip.run_cell_magic('timeit', '-n1 -r1', '%lmagic my line2')
619 617 nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line2')
620 618
621 619 def test_timeit_return():
622 620 """
623 621 test whether timeit -o return object
624 622 """
625 623
626 624 res = _ip.run_line_magic('timeit','-n10 -r10 -o 1')
627 625 assert(res is not None)
628 626
629 627 def test_timeit_quiet():
630 628 """
631 629 test quiet option of timeit magic
632 630 """
633 631 with tt.AssertNotPrints("loops"):
634 632 _ip.run_cell("%timeit -n1 -r1 -q 1")
635 633
636 634 def test_timeit_return_quiet():
637 635 with tt.AssertNotPrints("loops"):
638 636 res = _ip.run_line_magic('timeit', '-n1 -r1 -q -o 1')
639 637 assert (res is not None)
640 638
641 639 def test_timeit_invalid_return():
642 640 with nt.assert_raises_regex(SyntaxError, "outside function"):
643 641 _ip.run_line_magic('timeit', 'return')
644 642
645 643 @dec.skipif(execution.profile is None)
646 644 def test_prun_special_syntax():
647 645 "Test %%prun with IPython special syntax"
648 646 @register_line_magic
649 647 def lmagic(line):
650 648 ip = get_ipython()
651 649 ip.user_ns['lmagic_out'] = line
652 650
653 651 # line mode test
654 652 _ip.run_line_magic('prun', '-q %lmagic my line')
655 653 nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line')
656 654 # cell mode test
657 655 _ip.run_cell_magic('prun', '-q', '%lmagic my line2')
658 656 nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line2')
659 657
660 658 @dec.skipif(execution.profile is None)
661 659 def test_prun_quotes():
662 660 "Test that prun does not clobber string escapes (GH #1302)"
663 661 _ip.magic(r"prun -q x = '\t'")
664 662 nt.assert_equal(_ip.user_ns['x'], '\t')
665 663
666 664 def test_extension():
667 665 # Debugging information for failures of this test
668 666 print('sys.path:')
669 667 for p in sys.path:
670 668 print(' ', p)
671 669 print('CWD', os.getcwd())
672 670
673 671 nt.assert_raises(ImportError, _ip.magic, "load_ext daft_extension")
674 672 daft_path = os.path.join(os.path.dirname(__file__), "daft_extension")
675 673 sys.path.insert(0, daft_path)
676 674 try:
677 675 _ip.user_ns.pop('arq', None)
678 676 invalidate_caches() # Clear import caches
679 677 _ip.magic("load_ext daft_extension")
680 678 nt.assert_equal(_ip.user_ns['arq'], 185)
681 679 _ip.magic("unload_ext daft_extension")
682 680 assert 'arq' not in _ip.user_ns
683 681 finally:
684 682 sys.path.remove(daft_path)
685 683
686 684
687 685 def test_notebook_export_json():
688 686 _ip = get_ipython()
689 687 _ip.history_manager.reset() # Clear any existing history.
690 688 cmds = [u"a=1", u"def b():\n return a**2", u"print('noël, été', b())"]
691 689 for i, cmd in enumerate(cmds, start=1):
692 690 _ip.history_manager.store_inputs(i, cmd)
693 691 with TemporaryDirectory() as td:
694 692 outfile = os.path.join(td, "nb.ipynb")
695 693 _ip.magic("notebook -e %s" % outfile)
696 694
697 695
698 696 class TestEnv(TestCase):
699 697
700 698 def test_env(self):
701 699 env = _ip.magic("env")
702 700 self.assertTrue(isinstance(env, dict))
703 701
704 702 def test_env_get_set_simple(self):
705 703 env = _ip.magic("env var val1")
706 704 self.assertEqual(env, None)
707 705 self.assertEqual(os.environ['var'], 'val1')
708 706 self.assertEqual(_ip.magic("env var"), 'val1')
709 707 env = _ip.magic("env var=val2")
710 708 self.assertEqual(env, None)
711 709 self.assertEqual(os.environ['var'], 'val2')
712 710
713 711 def test_env_get_set_complex(self):
714 712 env = _ip.magic("env var 'val1 '' 'val2")
715 713 self.assertEqual(env, None)
716 714 self.assertEqual(os.environ['var'], "'val1 '' 'val2")
717 715 self.assertEqual(_ip.magic("env var"), "'val1 '' 'val2")
718 716 env = _ip.magic('env var=val2 val3="val4')
719 717 self.assertEqual(env, None)
720 718 self.assertEqual(os.environ['var'], 'val2 val3="val4')
721 719
722 720 def test_env_set_bad_input(self):
723 721 self.assertRaises(UsageError, lambda: _ip.magic("set_env var"))
724 722
725 723 def test_env_set_whitespace(self):
726 724 self.assertRaises(UsageError, lambda: _ip.magic("env var A=B"))
727 725
728 726
729 727 class CellMagicTestCase(TestCase):
730 728
731 729 def check_ident(self, magic):
732 730 # Manually called, we get the result
733 731 out = _ip.run_cell_magic(magic, 'a', 'b')
734 732 nt.assert_equal(out, ('a','b'))
735 733 # Via run_cell, it goes into the user's namespace via displayhook
736 734 _ip.run_cell('%%' + magic +' c\nd\n')
737 735 nt.assert_equal(_ip.user_ns['_'], ('c','d\n'))
738 736
739 737 def test_cell_magic_func_deco(self):
740 738 "Cell magic using simple decorator"
741 739 @register_cell_magic
742 740 def cellm(line, cell):
743 741 return line, cell
744 742
745 743 self.check_ident('cellm')
746 744
747 745 def test_cell_magic_reg(self):
748 746 "Cell magic manually registered"
749 747 def cellm(line, cell):
750 748 return line, cell
751 749
752 750 _ip.register_magic_function(cellm, 'cell', 'cellm2')
753 751 self.check_ident('cellm2')
754 752
755 753 def test_cell_magic_class(self):
756 754 "Cell magics declared via a class"
757 755 @magics_class
758 756 class MyMagics(Magics):
759 757
760 758 @cell_magic
761 759 def cellm3(self, line, cell):
762 760 return line, cell
763 761
764 762 _ip.register_magics(MyMagics)
765 763 self.check_ident('cellm3')
766 764
767 765 def test_cell_magic_class2(self):
768 766 "Cell magics declared via a class, #2"
769 767 @magics_class
770 768 class MyMagics2(Magics):
771 769
772 770 @cell_magic('cellm4')
773 771 def cellm33(self, line, cell):
774 772 return line, cell
775 773
776 774 _ip.register_magics(MyMagics2)
777 775 self.check_ident('cellm4')
778 776 # Check that nothing is registered as 'cellm33'
779 777 c33 = _ip.find_cell_magic('cellm33')
780 778 nt.assert_equal(c33, None)
781 779
782 780 def test_file():
783 781 """Basic %%writefile"""
784 782 ip = get_ipython()
785 783 with TemporaryDirectory() as td:
786 784 fname = os.path.join(td, 'file1')
787 785 ip.run_cell_magic("writefile", fname, u'\n'.join([
788 786 'line1',
789 787 'line2',
790 788 ]))
791 789 with open(fname) as f:
792 790 s = f.read()
793 791 nt.assert_in('line1\n', s)
794 792 nt.assert_in('line2', s)
795 793
796 794 @dec.skip_win32
797 795 def test_file_single_quote():
798 796 """Basic %%writefile with embedded single quotes"""
799 797 ip = get_ipython()
800 798 with TemporaryDirectory() as td:
801 799 fname = os.path.join(td, '\'file1\'')
802 800 ip.run_cell_magic("writefile", fname, u'\n'.join([
803 801 'line1',
804 802 'line2',
805 803 ]))
806 804 with open(fname) as f:
807 805 s = f.read()
808 806 nt.assert_in('line1\n', s)
809 807 nt.assert_in('line2', s)
810 808
811 809 @dec.skip_win32
812 810 def test_file_double_quote():
813 811 """Basic %%writefile with embedded double quotes"""
814 812 ip = get_ipython()
815 813 with TemporaryDirectory() as td:
816 814 fname = os.path.join(td, '"file1"')
817 815 ip.run_cell_magic("writefile", fname, u'\n'.join([
818 816 'line1',
819 817 'line2',
820 818 ]))
821 819 with open(fname) as f:
822 820 s = f.read()
823 821 nt.assert_in('line1\n', s)
824 822 nt.assert_in('line2', s)
825 823
826 824 def test_file_var_expand():
827 825 """%%writefile $filename"""
828 826 ip = get_ipython()
829 827 with TemporaryDirectory() as td:
830 828 fname = os.path.join(td, 'file1')
831 829 ip.user_ns['filename'] = fname
832 830 ip.run_cell_magic("writefile", '$filename', u'\n'.join([
833 831 'line1',
834 832 'line2',
835 833 ]))
836 834 with open(fname) as f:
837 835 s = f.read()
838 836 nt.assert_in('line1\n', s)
839 837 nt.assert_in('line2', s)
840 838
841 839 def test_file_unicode():
842 840 """%%writefile with unicode cell"""
843 841 ip = get_ipython()
844 842 with TemporaryDirectory() as td:
845 843 fname = os.path.join(td, 'file1')
846 844 ip.run_cell_magic("writefile", fname, u'\n'.join([
847 845 u'liné1',
848 846 u'liné2',
849 847 ]))
850 848 with io.open(fname, encoding='utf-8') as f:
851 849 s = f.read()
852 850 nt.assert_in(u'liné1\n', s)
853 851 nt.assert_in(u'liné2', s)
854 852
855 853 def test_file_amend():
856 854 """%%writefile -a amends files"""
857 855 ip = get_ipython()
858 856 with TemporaryDirectory() as td:
859 857 fname = os.path.join(td, 'file2')
860 858 ip.run_cell_magic("writefile", fname, u'\n'.join([
861 859 'line1',
862 860 'line2',
863 861 ]))
864 862 ip.run_cell_magic("writefile", "-a %s" % fname, u'\n'.join([
865 863 'line3',
866 864 'line4',
867 865 ]))
868 866 with open(fname) as f:
869 867 s = f.read()
870 868 nt.assert_in('line1\n', s)
871 869 nt.assert_in('line3\n', s)
872 870
873 871 def test_file_spaces():
874 872 """%%file with spaces in filename"""
875 873 ip = get_ipython()
876 874 with TemporaryWorkingDirectory() as td:
877 875 fname = "file name"
878 876 ip.run_cell_magic("file", '"%s"'%fname, u'\n'.join([
879 877 'line1',
880 878 'line2',
881 879 ]))
882 880 with open(fname) as f:
883 881 s = f.read()
884 882 nt.assert_in('line1\n', s)
885 883 nt.assert_in('line2', s)
886 884
887 885 def test_script_config():
888 886 ip = get_ipython()
889 887 ip.config.ScriptMagics.script_magics = ['whoda']
890 888 sm = script.ScriptMagics(shell=ip)
891 889 nt.assert_in('whoda', sm.magics['cell'])
892 890
893 891 @dec.skip_win32
894 892 def test_script_out():
895 893 ip = get_ipython()
896 894 ip.run_cell_magic("script", "--out output sh", "echo 'hi'")
897 895 nt.assert_equal(ip.user_ns['output'], 'hi\n')
898 896
899 897 @dec.skip_win32
900 898 def test_script_err():
901 899 ip = get_ipython()
902 900 ip.run_cell_magic("script", "--err error sh", "echo 'hello' >&2")
903 901 nt.assert_equal(ip.user_ns['error'], 'hello\n')
904 902
905 903 @dec.skip_win32
906 904 def test_script_out_err():
907 905 ip = get_ipython()
908 906 ip.run_cell_magic("script", "--out output --err error sh", "echo 'hi'\necho 'hello' >&2")
909 907 nt.assert_equal(ip.user_ns['output'], 'hi\n')
910 908 nt.assert_equal(ip.user_ns['error'], 'hello\n')
911 909
912 910 @dec.skip_win32
913 911 def test_script_bg_out():
914 912 ip = get_ipython()
915 913 ip.run_cell_magic("script", "--bg --out output sh", "echo 'hi'")
916 914
917 915 nt.assert_equal(ip.user_ns['output'].read(), b'hi\n')
918 916 ip.user_ns['output'].close()
919 917
920 918 @dec.skip_win32
921 919 def test_script_bg_err():
922 920 ip = get_ipython()
923 921 ip.run_cell_magic("script", "--bg --err error sh", "echo 'hello' >&2")
924 922 nt.assert_equal(ip.user_ns['error'].read(), b'hello\n')
925 923 ip.user_ns['error'].close()
926 924
927 925 @dec.skip_win32
928 926 def test_script_bg_out_err():
929 927 ip = get_ipython()
930 928 ip.run_cell_magic("script", "--bg --out output --err error sh", "echo 'hi'\necho 'hello' >&2")
931 929 nt.assert_equal(ip.user_ns['output'].read(), b'hi\n')
932 930 nt.assert_equal(ip.user_ns['error'].read(), b'hello\n')
933 931 ip.user_ns['output'].close()
934 932 ip.user_ns['error'].close()
935 933
936 934 def test_script_defaults():
937 935 ip = get_ipython()
938 936 for cmd in ['sh', 'bash', 'perl', 'ruby']:
939 937 try:
940 938 find_cmd(cmd)
941 939 except Exception:
942 940 pass
943 941 else:
944 942 nt.assert_in(cmd, ip.magics_manager.magics['cell'])
945 943
946 944
947 945 @magics_class
948 946 class FooFoo(Magics):
949 947 """class with both %foo and %%foo magics"""
950 948 @line_magic('foo')
951 949 def line_foo(self, line):
952 950 "I am line foo"
953 951 pass
954 952
955 953 @cell_magic("foo")
956 954 def cell_foo(self, line, cell):
957 955 "I am cell foo, not line foo"
958 956 pass
959 957
960 958 def test_line_cell_info():
961 959 """%%foo and %foo magics are distinguishable to inspect"""
962 960 ip = get_ipython()
963 961 ip.magics_manager.register(FooFoo)
964 962 oinfo = ip.object_inspect('foo')
965 963 nt.assert_true(oinfo['found'])
966 964 nt.assert_true(oinfo['ismagic'])
967 965
968 966 oinfo = ip.object_inspect('%%foo')
969 967 nt.assert_true(oinfo['found'])
970 968 nt.assert_true(oinfo['ismagic'])
971 969 nt.assert_equal(oinfo['docstring'], FooFoo.cell_foo.__doc__)
972 970
973 971 oinfo = ip.object_inspect('%foo')
974 972 nt.assert_true(oinfo['found'])
975 973 nt.assert_true(oinfo['ismagic'])
976 974 nt.assert_equal(oinfo['docstring'], FooFoo.line_foo.__doc__)
977 975
978 976 def test_multiple_magics():
979 977 ip = get_ipython()
980 978 foo1 = FooFoo(ip)
981 979 foo2 = FooFoo(ip)
982 980 mm = ip.magics_manager
983 981 mm.register(foo1)
984 982 nt.assert_true(mm.magics['line']['foo'].__self__ is foo1)
985 983 mm.register(foo2)
986 984 nt.assert_true(mm.magics['line']['foo'].__self__ is foo2)
987 985
988 986 def test_alias_magic():
989 987 """Test %alias_magic."""
990 988 ip = get_ipython()
991 989 mm = ip.magics_manager
992 990
993 991 # Basic operation: both cell and line magics are created, if possible.
994 992 ip.run_line_magic('alias_magic', 'timeit_alias timeit')
995 993 nt.assert_in('timeit_alias', mm.magics['line'])
996 994 nt.assert_in('timeit_alias', mm.magics['cell'])
997 995
998 996 # --cell is specified, line magic not created.
999 997 ip.run_line_magic('alias_magic', '--cell timeit_cell_alias timeit')
1000 998 nt.assert_not_in('timeit_cell_alias', mm.magics['line'])
1001 999 nt.assert_in('timeit_cell_alias', mm.magics['cell'])
1002 1000
1003 1001 # Test that line alias is created successfully.
1004 1002 ip.run_line_magic('alias_magic', '--line env_alias env')
1005 1003 nt.assert_equal(ip.run_line_magic('env', ''),
1006 1004 ip.run_line_magic('env_alias', ''))
1007 1005
1008 1006 # Test that line alias with parameters passed in is created successfully.
1009 1007 ip.run_line_magic('alias_magic', '--line history_alias history --params ' + shlex.quote('3'))
1010 1008 nt.assert_in('history_alias', mm.magics['line'])
1011 1009
1012 1010
1013 1011 def test_save():
1014 1012 """Test %save."""
1015 1013 ip = get_ipython()
1016 1014 ip.history_manager.reset() # Clear any existing history.
1017 1015 cmds = [u"a=1", u"def b():\n return a**2", u"print(a, b())"]
1018 1016 for i, cmd in enumerate(cmds, start=1):
1019 1017 ip.history_manager.store_inputs(i, cmd)
1020 1018 with TemporaryDirectory() as tmpdir:
1021 1019 file = os.path.join(tmpdir, "testsave.py")
1022 1020 ip.run_line_magic("save", "%s 1-10" % file)
1023 1021 with open(file) as f:
1024 1022 content = f.read()
1025 1023 nt.assert_equal(content.count(cmds[0]), 1)
1026 1024 nt.assert_in('coding: utf-8', content)
1027 1025 ip.run_line_magic("save", "-a %s 1-10" % file)
1028 1026 with open(file) as f:
1029 1027 content = f.read()
1030 1028 nt.assert_equal(content.count(cmds[0]), 2)
1031 1029 nt.assert_in('coding: utf-8', content)
1032 1030
1033 1031
1034 1032 def test_store():
1035 1033 """Test %store."""
1036 1034 ip = get_ipython()
1037 1035 ip.run_line_magic('load_ext', 'storemagic')
1038 1036
1039 1037 # make sure the storage is empty
1040 1038 ip.run_line_magic('store', '-z')
1041 1039 ip.user_ns['var'] = 42
1042 1040 ip.run_line_magic('store', 'var')
1043 1041 ip.user_ns['var'] = 39
1044 1042 ip.run_line_magic('store', '-r')
1045 1043 nt.assert_equal(ip.user_ns['var'], 42)
1046 1044
1047 1045 ip.run_line_magic('store', '-d var')
1048 1046 ip.user_ns['var'] = 39
1049 1047 ip.run_line_magic('store' , '-r')
1050 1048 nt.assert_equal(ip.user_ns['var'], 39)
1051 1049
1052 1050
1053 1051 def _run_edit_test(arg_s, exp_filename=None,
1054 1052 exp_lineno=-1,
1055 1053 exp_contents=None,
1056 1054 exp_is_temp=None):
1057 1055 ip = get_ipython()
1058 1056 M = code.CodeMagics(ip)
1059 1057 last_call = ['','']
1060 1058 opts,args = M.parse_options(arg_s,'prxn:')
1061 1059 filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call)
1062 1060
1063 1061 if exp_filename is not None:
1064 1062 nt.assert_equal(exp_filename, filename)
1065 1063 if exp_contents is not None:
1066 1064 with io.open(filename, 'r', encoding='utf-8') as f:
1067 1065 contents = f.read()
1068 1066 nt.assert_equal(exp_contents, contents)
1069 1067 if exp_lineno != -1:
1070 1068 nt.assert_equal(exp_lineno, lineno)
1071 1069 if exp_is_temp is not None:
1072 1070 nt.assert_equal(exp_is_temp, is_temp)
1073 1071
1074 1072
1075 1073 def test_edit_interactive():
1076 1074 """%edit on interactively defined objects"""
1077 1075 ip = get_ipython()
1078 1076 n = ip.execution_count
1079 1077 ip.run_cell(u"def foo(): return 1", store_history=True)
1080 1078
1081 1079 try:
1082 1080 _run_edit_test("foo")
1083 1081 except code.InteractivelyDefined as e:
1084 1082 nt.assert_equal(e.index, n)
1085 1083 else:
1086 1084 raise AssertionError("Should have raised InteractivelyDefined")
1087 1085
1088 1086
1089 1087 def test_edit_cell():
1090 1088 """%edit [cell id]"""
1091 1089 ip = get_ipython()
1092 1090
1093 1091 ip.run_cell(u"def foo(): return 1", store_history=True)
1094 1092
1095 1093 # test
1096 1094 _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True)
1097 1095
1098 1096 def test_bookmark():
1099 1097 ip = get_ipython()
1100 1098 ip.run_line_magic('bookmark', 'bmname')
1101 1099 with tt.AssertPrints('bmname'):
1102 1100 ip.run_line_magic('bookmark', '-l')
1103 1101 ip.run_line_magic('bookmark', '-d bmname')
1104 1102
1105 1103 def test_ls_magic():
1106 1104 ip = get_ipython()
1107 1105 json_formatter = ip.display_formatter.formatters['application/json']
1108 1106 json_formatter.enabled = True
1109 1107 lsmagic = ip.magic('lsmagic')
1110 1108 with warnings.catch_warnings(record=True) as w:
1111 1109 j = json_formatter(lsmagic)
1112 1110 nt.assert_equal(sorted(j), ['cell', 'line'])
1113 1111 nt.assert_equal(w, []) # no warnings
1114 1112
1115 1113 def test_strip_initial_indent():
1116 1114 def sii(s):
1117 1115 lines = s.splitlines()
1118 1116 return '\n'.join(code.strip_initial_indent(lines))
1119 1117
1120 1118 nt.assert_equal(sii(" a = 1\nb = 2"), "a = 1\nb = 2")
1121 1119 nt.assert_equal(sii(" a\n b\nc"), "a\n b\nc")
1122 1120 nt.assert_equal(sii("a\n b"), "a\n b")
1123 1121
1124 1122 def test_logging_magic_quiet_from_arg():
1125 1123 _ip.config.LoggingMagics.quiet = False
1126 1124 lm = logging.LoggingMagics(shell=_ip)
1127 1125 with TemporaryDirectory() as td:
1128 1126 try:
1129 1127 with tt.AssertNotPrints(re.compile("Activating.*")):
1130 1128 lm.logstart('-q {}'.format(
1131 1129 os.path.join(td, "quiet_from_arg.log")))
1132 1130 finally:
1133 1131 _ip.logger.logstop()
1134 1132
1135 1133 def test_logging_magic_quiet_from_config():
1136 1134 _ip.config.LoggingMagics.quiet = True
1137 1135 lm = logging.LoggingMagics(shell=_ip)
1138 1136 with TemporaryDirectory() as td:
1139 1137 try:
1140 1138 with tt.AssertNotPrints(re.compile("Activating.*")):
1141 1139 lm.logstart(os.path.join(td, "quiet_from_config.log"))
1142 1140 finally:
1143 1141 _ip.logger.logstop()
1144 1142
1145 1143
1146 1144 def test_logging_magic_not_quiet():
1147 1145 _ip.config.LoggingMagics.quiet = False
1148 1146 lm = logging.LoggingMagics(shell=_ip)
1149 1147 with TemporaryDirectory() as td:
1150 1148 try:
1151 1149 with tt.AssertPrints(re.compile("Activating.*")):
1152 1150 lm.logstart(os.path.join(td, "not_quiet.log"))
1153 1151 finally:
1154 1152 _ip.logger.logstop()
1155 1153
1156 1154
1157 1155 def test_time_no_var_expand():
1158 1156 _ip.user_ns['a'] = 5
1159 1157 _ip.user_ns['b'] = []
1160 1158 _ip.magic('time b.append("{a}")')
1161 1159 assert _ip.user_ns['b'] == ['{a}']
1162 1160
1163 1161
1164 1162 # this is slow, put at the end for local testing.
1165 1163 def test_timeit_arguments():
1166 1164 "Test valid timeit arguments, should not cause SyntaxError (GH #1269)"
1167 1165 if sys.version_info < (3,7):
1168 1166 _ip.magic("timeit -n1 -r1 ('#')")
1169 1167 else:
1170 1168 # 3.7 optimize no-op statement like above out, and complain there is
1171 1169 # nothing in the for loop.
1172 1170 _ip.magic("timeit -n1 -r1 a=('#')")
@@ -1,196 +1,192 b''
1 1 """Tests for various magic functions specific to the terminal frontend.
2 2
3 3 Needs to be run by nose (to make ipython session available).
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Imports
8 8 #-----------------------------------------------------------------------------
9 9
10 10 import sys
11 11 from io import StringIO
12 12 from unittest import TestCase
13 13
14 14 import nose.tools as nt
15 15
16 16 from IPython.testing import tools as tt
17 17
18 18 #-----------------------------------------------------------------------------
19 # Globals
20 #-----------------------------------------------------------------------------
21
22 #-----------------------------------------------------------------------------
23 19 # Test functions begin
24 20 #-----------------------------------------------------------------------------
25 21
26 22 def check_cpaste(code, should_fail=False):
27 23 """Execute code via 'cpaste' and ensure it was executed, unless
28 24 should_fail is set.
29 25 """
30 26 ip.user_ns['code_ran'] = False
31 27
32 28 src = StringIO()
33 29 if not hasattr(src, 'encoding'):
34 30 # IPython expects stdin to have an encoding attribute
35 31 src.encoding = None
36 32 src.write(code)
37 33 src.write('\n--\n')
38 34 src.seek(0)
39 35
40 36 stdin_save = sys.stdin
41 37 sys.stdin = src
42 38
43 39 try:
44 40 context = tt.AssertPrints if should_fail else tt.AssertNotPrints
45 41 with context("Traceback (most recent call last)"):
46 42 ip.magic('cpaste')
47 43
48 44 if not should_fail:
49 45 assert ip.user_ns['code_ran'], "%r failed" % code
50 46 finally:
51 47 sys.stdin = stdin_save
52 48
53 49 def test_cpaste():
54 50 """Test cpaste magic"""
55 51
56 52 def runf():
57 53 """Marker function: sets a flag when executed.
58 54 """
59 55 ip.user_ns['code_ran'] = True
60 56 return 'runf' # return string so '+ runf()' doesn't result in success
61 57
62 58 tests = {'pass': ["runf()",
63 59 "In [1]: runf()",
64 60 "In [1]: if 1:\n ...: runf()",
65 61 "> > > runf()",
66 62 ">>> runf()",
67 63 " >>> runf()",
68 64 ],
69 65
70 66 'fail': ["1 + runf()",
71 67 "++ runf()",
72 68 ]}
73 69
74 70 ip.user_ns['runf'] = runf
75 71
76 72 for code in tests['pass']:
77 73 check_cpaste(code)
78 74
79 75 for code in tests['fail']:
80 76 check_cpaste(code, should_fail=True)
81 77
82 78
83 79 class PasteTestCase(TestCase):
84 80 """Multiple tests for clipboard pasting"""
85 81
86 82 def paste(self, txt, flags='-q'):
87 83 """Paste input text, by default in quiet mode"""
88 84 ip.hooks.clipboard_get = lambda : txt
89 85 ip.magic('paste '+flags)
90 86
91 87 def setUp(self):
92 88 # Inject fake clipboard hook but save original so we can restore it later
93 89 self.original_clip = ip.hooks.clipboard_get
94 90
95 91 def tearDown(self):
96 92 # Restore original hook
97 93 ip.hooks.clipboard_get = self.original_clip
98 94
99 95 def test_paste(self):
100 96 ip.user_ns.pop('x', None)
101 97 self.paste('x = 1')
102 98 nt.assert_equal(ip.user_ns['x'], 1)
103 99 ip.user_ns.pop('x')
104 100
105 101 def test_paste_pyprompt(self):
106 102 ip.user_ns.pop('x', None)
107 103 self.paste('>>> x=2')
108 104 nt.assert_equal(ip.user_ns['x'], 2)
109 105 ip.user_ns.pop('x')
110 106
111 107 def test_paste_py_multi(self):
112 108 self.paste("""
113 109 >>> x = [1,2,3]
114 110 >>> y = []
115 111 >>> for i in x:
116 112 ... y.append(i**2)
117 113 ...
118 114 """)
119 115 nt.assert_equal(ip.user_ns['x'], [1,2,3])
120 116 nt.assert_equal(ip.user_ns['y'], [1,4,9])
121 117
122 118 def test_paste_py_multi_r(self):
123 119 "Now, test that self.paste -r works"
124 120 self.test_paste_py_multi()
125 121 nt.assert_equal(ip.user_ns.pop('x'), [1,2,3])
126 122 nt.assert_equal(ip.user_ns.pop('y'), [1,4,9])
127 123 nt.assert_false('x' in ip.user_ns)
128 124 ip.magic('paste -r')
129 125 nt.assert_equal(ip.user_ns['x'], [1,2,3])
130 126 nt.assert_equal(ip.user_ns['y'], [1,4,9])
131 127
132 128 def test_paste_email(self):
133 129 "Test pasting of email-quoted contents"
134 130 self.paste("""\
135 131 >> def foo(x):
136 132 >> return x + 1
137 133 >> xx = foo(1.1)""")
138 134 nt.assert_equal(ip.user_ns['xx'], 2.1)
139 135
140 136 def test_paste_email2(self):
141 137 "Email again; some programs add a space also at each quoting level"
142 138 self.paste("""\
143 139 > > def foo(x):
144 140 > > return x + 1
145 141 > > yy = foo(2.1) """)
146 142 nt.assert_equal(ip.user_ns['yy'], 3.1)
147 143
148 144 def test_paste_email_py(self):
149 145 "Email quoting of interactive input"
150 146 self.paste("""\
151 147 >> >>> def f(x):
152 148 >> ... return x+1
153 149 >> ...
154 150 >> >>> zz = f(2.5) """)
155 151 nt.assert_equal(ip.user_ns['zz'], 3.5)
156 152
157 153 def test_paste_echo(self):
158 154 "Also test self.paste echoing, by temporarily faking the writer"
159 155 w = StringIO()
160 156 writer = ip.write
161 157 ip.write = w.write
162 158 code = """
163 159 a = 100
164 160 b = 200"""
165 161 try:
166 162 self.paste(code,'')
167 163 out = w.getvalue()
168 164 finally:
169 165 ip.write = writer
170 166 nt.assert_equal(ip.user_ns['a'], 100)
171 167 nt.assert_equal(ip.user_ns['b'], 200)
172 168 assert out == code+"\n## -- End pasted text --\n"
173 169
174 170 def test_paste_leading_commas(self):
175 171 "Test multiline strings with leading commas"
176 172 tm = ip.magics_manager.registry['TerminalMagics']
177 173 s = '''\
178 174 a = """
179 175 ,1,2,3
180 176 """'''
181 177 ip.user_ns.pop('foo', None)
182 178 tm.store_or_execute(s, 'foo')
183 179 nt.assert_in('foo', ip.user_ns)
184 180
185 181
186 182 def test_paste_trailing_question(self):
187 183 "Test pasting sources with trailing question marks"
188 184 tm = ip.magics_manager.registry['TerminalMagics']
189 185 s = '''\
190 186 def funcfoo():
191 187 if True: #am i true?
192 188 return 'fooresult'
193 189 '''
194 190 ip.user_ns.pop('funcfoo', None)
195 191 self.paste(s)
196 192 nt.assert_equal(ip.user_ns['funcfoo'](), 'fooresult')
@@ -1,437 +1,439 b''
1 1 """Tests for the object inspection functionality.
2 2 """
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7
8 8 from inspect import signature, Signature, Parameter
9 9 import os
10 10 import re
11 11
12 12 import nose.tools as nt
13 13
14 14 from .. import oinspect
15 15
16 16 from decorator import decorator
17 17
18 18 from IPython.testing.tools import AssertPrints, AssertNotPrints
19 19 from IPython.utils.path import compress_user
20 20
21 21
22 22 #-----------------------------------------------------------------------------
23 23 # Globals and constants
24 24 #-----------------------------------------------------------------------------
25 25
26 inspector = oinspect.Inspector()
27 ip = get_ipython()
26 inspector = None
27
28 def setup_module():
29 global inspector
30 inspector = oinspect.Inspector()
31
28 32
29 33 #-----------------------------------------------------------------------------
30 34 # Local utilities
31 35 #-----------------------------------------------------------------------------
32 36
33 37 # WARNING: since this test checks the line number where a function is
34 38 # defined, if any code is inserted above, the following line will need to be
35 39 # updated. Do NOT insert any whitespace between the next line and the function
36 40 # definition below.
37 THIS_LINE_NUMBER = 37 # Put here the actual number of this line
41 THIS_LINE_NUMBER = 41 # Put here the actual number of this line
38 42
39 43 from unittest import TestCase
40 44
41 45 class Test(TestCase):
42 46
43 47 def test_find_source_lines(self):
44 48 self.assertEqual(oinspect.find_source_lines(Test.test_find_source_lines),
45 49 THIS_LINE_NUMBER+6)
46 50
47 51
48 52 # A couple of utilities to ensure these tests work the same from a source or a
49 53 # binary install
50 54 def pyfile(fname):
51 55 return os.path.normcase(re.sub('.py[co]$', '.py', fname))
52 56
53 57
54 58 def match_pyfiles(f1, f2):
55 59 nt.assert_equal(pyfile(f1), pyfile(f2))
56 60
57 61
58 62 def test_find_file():
59 63 match_pyfiles(oinspect.find_file(test_find_file), os.path.abspath(__file__))
60 64
61 65
62 66 def test_find_file_decorated1():
63 67
64 68 @decorator
65 69 def noop1(f):
66 70 def wrapper(*a, **kw):
67 71 return f(*a, **kw)
68 72 return wrapper
69 73
70 74 @noop1
71 75 def f(x):
72 76 "My docstring"
73 77
74 78 match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__))
75 79 nt.assert_equal(f.__doc__, "My docstring")
76 80
77 81
78 82 def test_find_file_decorated2():
79 83
80 84 @decorator
81 85 def noop2(f, *a, **kw):
82 86 return f(*a, **kw)
83 87
84 88 @noop2
85 89 @noop2
86 90 @noop2
87 91 def f(x):
88 92 "My docstring 2"
89 93
90 94 match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__))
91 95 nt.assert_equal(f.__doc__, "My docstring 2")
92 96
93 97
94 98 def test_find_file_magic():
95 99 run = ip.find_line_magic('run')
96 100 nt.assert_not_equal(oinspect.find_file(run), None)
97 101
98 102
99 103 # A few generic objects we can then inspect in the tests below
100 104
101 105 class Call(object):
102 106 """This is the class docstring."""
103 107
104 108 def __init__(self, x, y=1):
105 109 """This is the constructor docstring."""
106 110
107 111 def __call__(self, *a, **kw):
108 112 """This is the call docstring."""
109 113
110 114 def method(self, x, z=2):
111 115 """Some method's docstring"""
112 116
113 117 class HasSignature(object):
114 118 """This is the class docstring."""
115 119 __signature__ = Signature([Parameter('test', Parameter.POSITIONAL_OR_KEYWORD)])
116 120
117 121 def __init__(self, *args):
118 122 """This is the init docstring"""
119 123
120 124
121 125 class SimpleClass(object):
122 126 def method(self, x, z=2):
123 127 """Some method's docstring"""
124 128
125 129
126
127 130 class Awkward(object):
128 131 def __getattr__(self, name):
129 132 raise Exception(name)
130 133
131 134 class NoBoolCall:
132 135 """
133 136 callable with `__bool__` raising should still be inspect-able.
134 137 """
135 138
136 139 def __call__(self):
137 140 """does nothing"""
138 141 pass
139 142
140 143 def __bool__(self):
141 144 """just raise NotImplemented"""
142 145 raise NotImplementedError('Must be implemented')
143 146
144 147
145 148 class SerialLiar(object):
146 149 """Attribute accesses always get another copy of the same class.
147 150
148 151 unittest.mock.call does something similar, but it's not ideal for testing
149 152 as the failure mode is to eat all your RAM. This gives up after 10k levels.
150 153 """
151 154 def __init__(self, max_fibbing_twig, lies_told=0):
152 155 if lies_told > 10000:
153 156 raise RuntimeError('Nose too long, honesty is the best policy')
154 157 self.max_fibbing_twig = max_fibbing_twig
155 158 self.lies_told = lies_told
156 159 max_fibbing_twig[0] = max(max_fibbing_twig[0], lies_told)
157 160
158 161 def __getattr__(self, item):
159 162 return SerialLiar(self.max_fibbing_twig, self.lies_told + 1)
160 163
161 164 #-----------------------------------------------------------------------------
162 165 # Tests
163 166 #-----------------------------------------------------------------------------
164 167
165 168 def test_info():
166 169 "Check that Inspector.info fills out various fields as expected."
167 170 i = inspector.info(Call, oname='Call')
168 171 nt.assert_equal(i['type_name'], 'type')
169 172 expted_class = str(type(type)) # <class 'type'> (Python 3) or <type 'type'>
170 173 nt.assert_equal(i['base_class'], expted_class)
171 174 nt.assert_regex(i['string_form'], "<class 'IPython.core.tests.test_oinspect.Call'( at 0x[0-9a-f]{1,9})?>")
172 175 fname = __file__
173 176 if fname.endswith(".pyc"):
174 177 fname = fname[:-1]
175 178 # case-insensitive comparison needed on some filesystems
176 179 # e.g. Windows:
177 180 nt.assert_equal(i['file'].lower(), compress_user(fname).lower())
178 181 nt.assert_equal(i['definition'], None)
179 182 nt.assert_equal(i['docstring'], Call.__doc__)
180 183 nt.assert_equal(i['source'], None)
181 184 nt.assert_true(i['isclass'])
182 185 nt.assert_equal(i['init_definition'], "Call(x, y=1)")
183 186 nt.assert_equal(i['init_docstring'], Call.__init__.__doc__)
184 187
185 188 i = inspector.info(Call, detail_level=1)
186 189 nt.assert_not_equal(i['source'], None)
187 190 nt.assert_equal(i['docstring'], None)
188 191
189 192 c = Call(1)
190 193 c.__doc__ = "Modified instance docstring"
191 194 i = inspector.info(c)
192 195 nt.assert_equal(i['type_name'], 'Call')
193 196 nt.assert_equal(i['docstring'], "Modified instance docstring")
194 197 nt.assert_equal(i['class_docstring'], Call.__doc__)
195 198 nt.assert_equal(i['init_docstring'], Call.__init__.__doc__)
196 199 nt.assert_equal(i['call_docstring'], Call.__call__.__doc__)
197 200
198 201 def test_class_signature():
199 202 info = inspector.info(HasSignature, 'HasSignature')
200 203 nt.assert_equal(info['init_definition'], "HasSignature(test)")
201 204 nt.assert_equal(info['init_docstring'], HasSignature.__init__.__doc__)
202 205
203 206 def test_info_awkward():
204 207 # Just test that this doesn't throw an error.
205 208 inspector.info(Awkward())
206 209
207 210 def test_bool_raise():
208 211 inspector.info(NoBoolCall())
209 212
210 213 def test_info_serialliar():
211 214 fib_tracker = [0]
212 215 inspector.info(SerialLiar(fib_tracker))
213 216
214 217 # Nested attribute access should be cut off at 100 levels deep to avoid
215 218 # infinite loops: https://github.com/ipython/ipython/issues/9122
216 219 nt.assert_less(fib_tracker[0], 9000)
217 220
218 221 def support_function_one(x, y=2, *a, **kw):
219 222 """A simple function."""
220 223
221 224 def test_calldef_none():
222 225 # We should ignore __call__ for all of these.
223 226 for obj in [support_function_one, SimpleClass().method, any, str.upper]:
224 print(obj)
225 227 i = inspector.info(obj)
226 228 nt.assert_is(i['call_def'], None)
227 229
228 230 def f_kwarg(pos, *, kwonly):
229 231 pass
230 232
231 233 def test_definition_kwonlyargs():
232 234 i = inspector.info(f_kwarg, oname='f_kwarg') # analysis:ignore
233 235 nt.assert_equal(i['definition'], "f_kwarg(pos, *, kwonly)")
234 236
235 237 def test_getdoc():
236 238 class A(object):
237 239 """standard docstring"""
238 240 pass
239 241
240 242 class B(object):
241 243 """standard docstring"""
242 244 def getdoc(self):
243 245 return "custom docstring"
244 246
245 247 class C(object):
246 248 """standard docstring"""
247 249 def getdoc(self):
248 250 return None
249 251
250 252 a = A()
251 253 b = B()
252 254 c = C()
253 255
254 256 nt.assert_equal(oinspect.getdoc(a), "standard docstring")
255 257 nt.assert_equal(oinspect.getdoc(b), "custom docstring")
256 258 nt.assert_equal(oinspect.getdoc(c), "standard docstring")
257 259
258 260
259 261 def test_empty_property_has_no_source():
260 262 i = inspector.info(property(), detail_level=1)
261 263 nt.assert_is(i['source'], None)
262 264
263 265
264 266 def test_property_sources():
265 267 import posixpath
266 268 # A simple adder whose source and signature stays
267 269 # the same across Python distributions
268 270 def simple_add(a, b):
269 271 "Adds two numbers"
270 272 return a + b
271 273
272 274 class A(object):
273 275 @property
274 276 def foo(self):
275 277 return 'bar'
276 278
277 279 foo = foo.setter(lambda self, v: setattr(self, 'bar', v))
278 280
279 281 dname = property(posixpath.dirname)
280 282 adder = property(simple_add)
281 283
282 284 i = inspector.info(A.foo, detail_level=1)
283 285 nt.assert_in('def foo(self):', i['source'])
284 286 nt.assert_in('lambda self, v:', i['source'])
285 287
286 288 i = inspector.info(A.dname, detail_level=1)
287 289 nt.assert_in('def dirname(p)', i['source'])
288 290
289 291 i = inspector.info(A.adder, detail_level=1)
290 292 nt.assert_in('def simple_add(a, b)', i['source'])
291 293
292 294
293 295 def test_property_docstring_is_in_info_for_detail_level_0():
294 296 class A(object):
295 297 @property
296 298 def foobar(self):
297 299 """This is `foobar` property."""
298 300 pass
299 301
300 302 ip.user_ns['a_obj'] = A()
301 303 nt.assert_equal(
302 304 'This is `foobar` property.',
303 305 ip.object_inspect('a_obj.foobar', detail_level=0)['docstring'])
304 306
305 307 ip.user_ns['a_cls'] = A
306 308 nt.assert_equal(
307 309 'This is `foobar` property.',
308 310 ip.object_inspect('a_cls.foobar', detail_level=0)['docstring'])
309 311
310 312
311 313 def test_pdef():
312 314 # See gh-1914
313 315 def foo(): pass
314 316 inspector.pdef(foo, 'foo')
315 317
316 318
317 319 def test_pinfo_nonascii():
318 320 # See gh-1177
319 321 from . import nonascii2
320 322 ip.user_ns['nonascii2'] = nonascii2
321 323 ip._inspect('pinfo', 'nonascii2', detail_level=1)
322 324
323 325 def test_pinfo_type():
324 326 """
325 327 type can fail in various edge case, for example `type.__subclass__()`
326 328 """
327 329 ip._inspect('pinfo', 'type')
328 330
329 331
330 332 def test_pinfo_docstring_no_source():
331 333 """Docstring should be included with detail_level=1 if there is no source"""
332 334 with AssertPrints('Docstring:'):
333 335 ip._inspect('pinfo', 'str.format', detail_level=0)
334 336 with AssertPrints('Docstring:'):
335 337 ip._inspect('pinfo', 'str.format', detail_level=1)
336 338
337 339
338 340 def test_pinfo_no_docstring_if_source():
339 341 """Docstring should not be included with detail_level=1 if source is found"""
340 342 def foo():
341 343 """foo has a docstring"""
342 344
343 345 ip.user_ns['foo'] = foo
344 346
345 347 with AssertPrints('Docstring:'):
346 348 ip._inspect('pinfo', 'foo', detail_level=0)
347 349 with AssertPrints('Source:'):
348 350 ip._inspect('pinfo', 'foo', detail_level=1)
349 351 with AssertNotPrints('Docstring:'):
350 352 ip._inspect('pinfo', 'foo', detail_level=1)
351 353
352 354
353 355 def test_pinfo_docstring_if_detail_and_no_source():
354 356 """ Docstring should be displayed if source info not available """
355 357 obj_def = '''class Foo(object):
356 358 """ This is a docstring for Foo """
357 359 def bar(self):
358 360 """ This is a docstring for Foo.bar """
359 361 pass
360 362 '''
361 363
362 364 ip.run_cell(obj_def)
363 365 ip.run_cell('foo = Foo()')
364 366
365 367 with AssertNotPrints("Source:"):
366 368 with AssertPrints('Docstring:'):
367 369 ip._inspect('pinfo', 'foo', detail_level=0)
368 370 with AssertPrints('Docstring:'):
369 371 ip._inspect('pinfo', 'foo', detail_level=1)
370 372 with AssertPrints('Docstring:'):
371 373 ip._inspect('pinfo', 'foo.bar', detail_level=0)
372 374
373 375 with AssertNotPrints('Docstring:'):
374 376 with AssertPrints('Source:'):
375 377 ip._inspect('pinfo', 'foo.bar', detail_level=1)
376 378
377 379
378 380 def test_pinfo_magic():
379 381 with AssertPrints('Docstring:'):
380 382 ip._inspect('pinfo', 'lsmagic', detail_level=0)
381 383
382 384 with AssertPrints('Source:'):
383 385 ip._inspect('pinfo', 'lsmagic', detail_level=1)
384 386
385 387
386 388 def test_init_colors():
387 389 # ensure colors are not present in signature info
388 390 info = inspector.info(HasSignature)
389 391 init_def = info['init_definition']
390 392 nt.assert_not_in('[0m', init_def)
391 393
392 394
393 395 def test_builtin_init():
394 396 info = inspector.info(list)
395 397 init_def = info['init_definition']
396 398 nt.assert_is_not_none(init_def)
397 399
398 400
399 401 def test_render_signature_short():
400 402 def short_fun(a=1): pass
401 403 sig = oinspect._render_signature(
402 404 signature(short_fun),
403 405 short_fun.__name__,
404 406 )
405 407 nt.assert_equal(sig, 'short_fun(a=1)')
406 408
407 409
408 410 def test_render_signature_long():
409 411 from typing import Optional
410 412
411 413 def long_function(
412 414 a_really_long_parameter: int,
413 415 and_another_long_one: bool = False,
414 416 let_us_make_sure_this_is_looong: Optional[str] = None,
415 417 ) -> bool: pass
416 418
417 419 sig = oinspect._render_signature(
418 420 signature(long_function),
419 421 long_function.__name__,
420 422 )
421 423 nt.assert_in(sig, [
422 424 # Python >=3.7
423 425 '''\
424 426 long_function(
425 427 a_really_long_parameter: int,
426 428 and_another_long_one: bool = False,
427 429 let_us_make_sure_this_is_looong: Union[str, NoneType] = None,
428 430 ) -> bool\
429 431 ''', # Python <=3.6
430 432 '''\
431 433 long_function(
432 434 a_really_long_parameter:int,
433 435 and_another_long_one:bool=False,
434 436 let_us_make_sure_this_is_looong:Union[str, NoneType]=None,
435 437 ) -> bool\
436 438 ''',
437 439 ])
@@ -1,8 +1,8 b''
1 1 try:
2 from numpy.testing.noseclasses import KnownFailure, knownfailureif
2 from numpy.testing import KnownFailure, knownfailureif
3 3 except ImportError:
4 4 from ._decorators import knownfailureif
5 5 try:
6 6 from ._numpy_testing_noseclasses import KnownFailure
7 7 except ImportError:
8 8 pass
@@ -1,492 +1,491 b''
1 1 # -*- coding: utf-8 -*-
2 2 """IPython Test Process Controller
3 3
4 4 This module runs one or more subprocesses which will actually run the IPython
5 5 test suite.
6 6
7 7 """
8 8
9 9 # Copyright (c) IPython Development Team.
10 10 # Distributed under the terms of the Modified BSD License.
11 11
12 12
13 13 import argparse
14 14 import multiprocessing.pool
15 15 import os
16 16 import stat
17 17 import shutil
18 18 import signal
19 19 import sys
20 20 import subprocess
21 21 import time
22 22
23 23 from .iptest import (
24 24 have, test_group_names as py_test_group_names, test_sections, StreamCapturer,
25 25 )
26 26 from IPython.utils.path import compress_user
27 27 from IPython.utils.py3compat import decode
28 28 from IPython.utils.sysinfo import get_sys_info
29 29 from IPython.utils.tempdir import TemporaryDirectory
30 30
31 31 class TestController:
32 32 """Run tests in a subprocess
33 33 """
34 34 #: str, IPython test suite to be executed.
35 35 section = None
36 36 #: list, command line arguments to be executed
37 37 cmd = None
38 38 #: dict, extra environment variables to set for the subprocess
39 39 env = None
40 40 #: list, TemporaryDirectory instances to clear up when the process finishes
41 41 dirs = None
42 42 #: subprocess.Popen instance
43 43 process = None
44 44 #: str, process stdout+stderr
45 45 stdout = None
46 46
47 47 def __init__(self):
48 48 self.cmd = []
49 49 self.env = {}
50 50 self.dirs = []
51 51
52 def setup(self):
52 def setUp(self):
53 53 """Create temporary directories etc.
54 54
55 55 This is only called when we know the test group will be run. Things
56 56 created here may be cleaned up by self.cleanup().
57 57 """
58 58 pass
59 59
60 60 def launch(self, buffer_output=False, capture_output=False):
61 61 # print('*** ENV:', self.env) # dbg
62 62 # print('*** CMD:', self.cmd) # dbg
63 63 env = os.environ.copy()
64 64 env.update(self.env)
65 65 if buffer_output:
66 66 capture_output = True
67 67 self.stdout_capturer = c = StreamCapturer(echo=not buffer_output)
68 68 c.start()
69 69 stdout = c.writefd if capture_output else None
70 70 stderr = subprocess.STDOUT if capture_output else None
71 71 self.process = subprocess.Popen(self.cmd, stdout=stdout,
72 72 stderr=stderr, env=env)
73 73
74 74 def wait(self):
75 75 self.process.wait()
76 76 self.stdout_capturer.halt()
77 77 self.stdout = self.stdout_capturer.get_buffer()
78 78 return self.process.returncode
79 79
80 80 def cleanup_process(self):
81 81 """Cleanup on exit by killing any leftover processes."""
82 82 subp = self.process
83 83 if subp is None or (subp.poll() is not None):
84 84 return # Process doesn't exist, or is already dead.
85 85
86 86 try:
87 87 print('Cleaning up stale PID: %d' % subp.pid)
88 88 subp.kill()
89 89 except: # (OSError, WindowsError) ?
90 90 # This is just a best effort, if we fail or the process was
91 91 # really gone, ignore it.
92 92 pass
93 93 else:
94 94 for i in range(10):
95 95 if subp.poll() is None:
96 96 time.sleep(0.1)
97 97 else:
98 98 break
99 99
100 100 if subp.poll() is None:
101 101 # The process did not die...
102 102 print('... failed. Manual cleanup may be required.')
103 103
104 104 def cleanup(self):
105 105 "Kill process if it's still alive, and clean up temporary directories"
106 106 self.cleanup_process()
107 107 for td in self.dirs:
108 108 td.cleanup()
109 109
110 110 __del__ = cleanup
111 111
112 112
113 113 class PyTestController(TestController):
114 114 """Run Python tests using IPython.testing.iptest"""
115 115 #: str, Python command to execute in subprocess
116 116 pycmd = None
117 117
118 118 def __init__(self, section, options):
119 119 """Create new test runner."""
120 120 TestController.__init__(self)
121 121 self.section = section
122 122 # pycmd is put into cmd[2] in PyTestController.launch()
123 123 self.cmd = [sys.executable, '-c', None, section]
124 124 self.pycmd = "from IPython.testing.iptest import run_iptest; run_iptest()"
125 125 self.options = options
126 126
127 127 def setup(self):
128 128 ipydir = TemporaryDirectory()
129 129 self.dirs.append(ipydir)
130 130 self.env['IPYTHONDIR'] = ipydir.name
131 131 self.workingdir = workingdir = TemporaryDirectory()
132 132 self.dirs.append(workingdir)
133 133 self.env['IPTEST_WORKING_DIR'] = workingdir.name
134 134 # This means we won't get odd effects from our own matplotlib config
135 135 self.env['MPLCONFIGDIR'] = workingdir.name
136 136 # For security reasons (http://bugs.python.org/issue16202), use
137 137 # a temporary directory to which other users have no access.
138 138 self.env['TMPDIR'] = workingdir.name
139 139
140 140 # Add a non-accessible directory to PATH (see gh-7053)
141 141 noaccess = os.path.join(self.workingdir.name, "_no_access_")
142 142 self.noaccess = noaccess
143 143 os.mkdir(noaccess, 0)
144 144
145 145 PATH = os.environ.get('PATH', '')
146 146 if PATH:
147 147 PATH = noaccess + os.pathsep + PATH
148 148 else:
149 149 PATH = noaccess
150 150 self.env['PATH'] = PATH
151 151
152 152 # From options:
153 153 if self.options.xunit:
154 154 self.add_xunit()
155 155 if self.options.coverage:
156 156 self.add_coverage()
157 157 self.env['IPTEST_SUBPROC_STREAMS'] = self.options.subproc_streams
158 158 self.cmd.extend(self.options.extra_args)
159 159
160 160 def cleanup(self):
161 161 """
162 162 Make the non-accessible directory created in setup() accessible
163 163 again, otherwise deleting the workingdir will fail.
164 164 """
165 165 os.chmod(self.noaccess, stat.S_IRWXU)
166 166 TestController.cleanup(self)
167 167
168 168 @property
169 169 def will_run(self):
170 170 try:
171 171 return test_sections[self.section].will_run
172 172 except KeyError:
173 173 return True
174 174
175 175 def add_xunit(self):
176 176 xunit_file = os.path.abspath(self.section + '.xunit.xml')
177 177 self.cmd.extend(['--with-xunit', '--xunit-file', xunit_file])
178 178
179 179 def add_coverage(self):
180 180 try:
181 181 sources = test_sections[self.section].includes
182 182 except KeyError:
183 183 sources = ['IPython']
184 184
185 185 coverage_rc = ("[run]\n"
186 186 "data_file = {data_file}\n"
187 187 "source =\n"
188 188 " {source}\n"
189 189 ).format(data_file=os.path.abspath('.coverage.'+self.section),
190 190 source="\n ".join(sources))
191 191 config_file = os.path.join(self.workingdir.name, '.coveragerc')
192 192 with open(config_file, 'w') as f:
193 193 f.write(coverage_rc)
194 194
195 195 self.env['COVERAGE_PROCESS_START'] = config_file
196 196 self.pycmd = "import coverage; coverage.process_startup(); " + self.pycmd
197 197
198 198 def launch(self, buffer_output=False):
199 199 self.cmd[2] = self.pycmd
200 200 super(PyTestController, self).launch(buffer_output=buffer_output)
201 201
202 202
203 203 def prepare_controllers(options):
204 204 """Returns two lists of TestController instances, those to run, and those
205 205 not to run."""
206 206 testgroups = options.testgroups
207 207 if not testgroups:
208 208 testgroups = py_test_group_names
209 209
210 210 controllers = [PyTestController(name, options) for name in testgroups]
211 211
212 212 to_run = [c for c in controllers if c.will_run]
213 213 not_run = [c for c in controllers if not c.will_run]
214 214 return to_run, not_run
215 215
216 216 def do_run(controller, buffer_output=True):
217 217 """Setup and run a test controller.
218 218
219 219 If buffer_output is True, no output is displayed, to avoid it appearing
220 220 interleaved. In this case, the caller is responsible for displaying test
221 221 output on failure.
222 222
223 223 Returns
224 224 -------
225 225 controller : TestController
226 226 The same controller as passed in, as a convenience for using map() type
227 227 APIs.
228 228 exitcode : int
229 229 The exit code of the test subprocess. Non-zero indicates failure.
230 230 """
231 231 try:
232 232 try:
233 233 controller.setup()
234 234 controller.launch(buffer_output=buffer_output)
235 235 except Exception:
236 236 import traceback
237 237 traceback.print_exc()
238 238 return controller, 1 # signal failure
239 239
240 240 exitcode = controller.wait()
241 241 return controller, exitcode
242 242
243 243 except KeyboardInterrupt:
244 244 return controller, -signal.SIGINT
245 245 finally:
246 246 controller.cleanup()
247 247
248 248 def report():
249 249 """Return a string with a summary report of test-related variables."""
250 250 inf = get_sys_info()
251 251 out = []
252 252 def _add(name, value):
253 253 out.append((name, value))
254 254
255 255 _add('IPython version', inf['ipython_version'])
256 256 _add('IPython commit', "{} ({})".format(inf['commit_hash'], inf['commit_source']))
257 257 _add('IPython package', compress_user(inf['ipython_path']))
258 258 _add('Python version', inf['sys_version'].replace('\n',''))
259 259 _add('sys.executable', compress_user(inf['sys_executable']))
260 260 _add('Platform', inf['platform'])
261 261
262 262 width = max(len(n) for (n,v) in out)
263 263 out = ["{:<{width}}: {}\n".format(n, v, width=width) for (n,v) in out]
264 264
265 265 avail = []
266 266 not_avail = []
267 267
268 268 for k, is_avail in have.items():
269 269 if is_avail:
270 270 avail.append(k)
271 271 else:
272 272 not_avail.append(k)
273 273
274 274 if avail:
275 275 out.append('\nTools and libraries available at test time:\n')
276 276 avail.sort()
277 277 out.append(' ' + ' '.join(avail)+'\n')
278 278
279 279 if not_avail:
280 280 out.append('\nTools and libraries NOT available at test time:\n')
281 281 not_avail.sort()
282 282 out.append(' ' + ' '.join(not_avail)+'\n')
283 283
284 284 return ''.join(out)
285 285
286 286 def run_iptestall(options):
287 287 """Run the entire IPython test suite by calling nose and trial.
288 288
289 289 This function constructs :class:`IPTester` instances for all IPython
290 290 modules and package and then runs each of them. This causes the modules
291 291 and packages of IPython to be tested each in their own subprocess using
292 292 nose.
293 293
294 294 Parameters
295 295 ----------
296 296
297 297 All parameters are passed as attributes of the options object.
298 298
299 299 testgroups : list of str
300 300 Run only these sections of the test suite. If empty, run all the available
301 301 sections.
302 302
303 303 fast : int or None
304 304 Run the test suite in parallel, using n simultaneous processes. If None
305 305 is passed, one process is used per CPU core. Default 1 (i.e. sequential)
306 306
307 307 inc_slow : bool
308 308 Include slow tests. By default, these tests aren't run.
309 309
310 310 url : unicode
311 311 Address:port to use when running the JS tests.
312 312
313 313 xunit : bool
314 314 Produce Xunit XML output. This is written to multiple foo.xunit.xml files.
315 315
316 316 coverage : bool or str
317 317 Measure code coverage from tests. True will store the raw coverage data,
318 318 or pass 'html' or 'xml' to get reports.
319 319
320 320 extra_args : list
321 321 Extra arguments to pass to the test subprocesses, e.g. '-v'
322 322 """
323 323 to_run, not_run = prepare_controllers(options)
324 324
325 325 def justify(ltext, rtext, width=70, fill='-'):
326 326 ltext += ' '
327 327 rtext = (' ' + rtext).rjust(width - len(ltext), fill)
328 328 return ltext + rtext
329 329
330 330 # Run all test runners, tracking execution time
331 331 failed = []
332 332 t_start = time.time()
333 333
334 334 print()
335 335 if options.fast == 1:
336 336 # This actually means sequential, i.e. with 1 job
337 337 for controller in to_run:
338 338 print('Test group:', controller.section)
339 339 sys.stdout.flush() # Show in correct order when output is piped
340 340 controller, res = do_run(controller, buffer_output=False)
341 341 if res:
342 342 failed.append(controller)
343 343 if res == -signal.SIGINT:
344 344 print("Interrupted")
345 345 break
346 346 print()
347 347
348 348 else:
349 349 # Run tests concurrently
350 350 try:
351 351 pool = multiprocessing.pool.ThreadPool(options.fast)
352 352 for (controller, res) in pool.imap_unordered(do_run, to_run):
353 353 res_string = 'OK' if res == 0 else 'FAILED'
354 354 print(justify('Test group: ' + controller.section, res_string))
355 355 if res:
356 356 print(decode(controller.stdout))
357 357 failed.append(controller)
358 358 if res == -signal.SIGINT:
359 359 print("Interrupted")
360 360 break
361 361 except KeyboardInterrupt:
362 362 return
363 363
364 364 for controller in not_run:
365 365 print(justify('Test group: ' + controller.section, 'NOT RUN'))
366 366
367 367 t_end = time.time()
368 368 t_tests = t_end - t_start
369 369 nrunners = len(to_run)
370 370 nfail = len(failed)
371 371 # summarize results
372 372 print('_'*70)
373 373 print('Test suite completed for system with the following information:')
374 374 print(report())
375 375 took = "Took %.3fs." % t_tests
376 376 print('Status: ', end='')
377 377 if not failed:
378 378 print('OK (%d test groups).' % nrunners, took)
379 379 else:
380 380 # If anything went wrong, point out what command to rerun manually to
381 381 # see the actual errors and individual summary
382 382 failed_sections = [c.section for c in failed]
383 383 print('ERROR - {} out of {} test groups failed ({}).'.format(nfail,
384 384 nrunners, ', '.join(failed_sections)), took)
385 385 print()
386 386 print('You may wish to rerun these, with:')
387 387 print(' iptest', *failed_sections)
388 388 print()
389 389
390 390 if options.coverage:
391 391 from coverage import coverage, CoverageException
392 392 cov = coverage(data_file='.coverage')
393 393 cov.combine()
394 394 cov.save()
395 395
396 396 # Coverage HTML report
397 397 if options.coverage == 'html':
398 398 html_dir = 'ipy_htmlcov'
399 399 shutil.rmtree(html_dir, ignore_errors=True)
400 400 print("Writing HTML coverage report to %s/ ... " % html_dir, end="")
401 401 sys.stdout.flush()
402 402
403 403 # Custom HTML reporter to clean up module names.
404 404 from coverage.html import HtmlReporter
405 405 class CustomHtmlReporter(HtmlReporter):
406 406 def find_code_units(self, morfs):
407 407 super(CustomHtmlReporter, self).find_code_units(morfs)
408 408 for cu in self.code_units:
409 409 nameparts = cu.name.split(os.sep)
410 410 if 'IPython' not in nameparts:
411 411 continue
412 412 ix = nameparts.index('IPython')
413 413 cu.name = '.'.join(nameparts[ix:])
414 414
415 415 # Reimplement the html_report method with our custom reporter
416 416 cov.get_data()
417 417 cov.config.from_args(omit='*{0}tests{0}*'.format(os.sep), html_dir=html_dir,
418 418 html_title='IPython test coverage',
419 419 )
420 420 reporter = CustomHtmlReporter(cov, cov.config)
421 421 reporter.report(None)
422 422 print('done.')
423 423
424 424 # Coverage XML report
425 425 elif options.coverage == 'xml':
426 426 try:
427 427 cov.xml_report(outfile='ipy_coverage.xml')
428 428 except CoverageException as e:
429 429 print('Generating coverage report failed. Are you running javascript tests only?')
430 430 import traceback
431 431 traceback.print_exc()
432 432
433 433 if failed:
434 434 # Ensure that our exit code indicates failure
435 435 sys.exit(1)
436 436
437 437 argparser = argparse.ArgumentParser(description='Run IPython test suite')
438 438 argparser.add_argument('testgroups', nargs='*',
439 439 help='Run specified groups of tests. If omitted, run '
440 440 'all tests.')
441 441 argparser.add_argument('--all', action='store_true',
442 442 help='Include slow tests not run by default.')
443 argparser.add_argument('--url', help="URL to use for the JS tests.")
444 443 argparser.add_argument('-j', '--fast', nargs='?', const=None, default=1, type=int,
445 444 help='Run test sections in parallel. This starts as many '
446 445 'processes as you have cores, or you can specify a number.')
447 446 argparser.add_argument('--xunit', action='store_true',
448 447 help='Produce Xunit XML results')
449 448 argparser.add_argument('--coverage', nargs='?', const=True, default=False,
450 449 help="Measure test coverage. Specify 'html' or "
451 450 "'xml' to get reports.")
452 451 argparser.add_argument('--subproc-streams', default='capture',
453 452 help="What to do with stdout/stderr from subprocesses. "
454 453 "'capture' (default), 'show' and 'discard' are the options.")
455 454
456 455 def default_options():
457 456 """Get an argparse Namespace object with the default arguments, to pass to
458 457 :func:`run_iptestall`.
459 458 """
460 459 options = argparser.parse_args([])
461 460 options.extra_args = []
462 461 return options
463 462
464 463 def main():
465 464 # iptest doesn't work correctly if the working directory is the
466 465 # root of the IPython source tree. Tell the user to avoid
467 466 # frustration.
468 467 if os.path.exists(os.path.join(os.getcwd(),
469 468 'IPython', 'testing', '__main__.py')):
470 469 print("Don't run iptest from the IPython source directory",
471 470 file=sys.stderr)
472 471 sys.exit(1)
473 472 # Arguments after -- should be passed through to nose. Argparse treats
474 473 # everything after -- as regular positional arguments, so we separate them
475 474 # first.
476 475 try:
477 476 ix = sys.argv.index('--')
478 477 except ValueError:
479 478 to_parse = sys.argv[1:]
480 479 extra_args = []
481 480 else:
482 481 to_parse = sys.argv[1:ix]
483 482 extra_args = sys.argv[ix+1:]
484 483
485 484 options = argparser.parse_args(to_parse)
486 485 options.extra_args = extra_args
487 486
488 487 run_iptestall(options)
489 488
490 489
491 490 if __name__ == '__main__':
492 491 main()
@@ -1,177 +1,178 b''
1 1 """Experimental code for cleaner support of IPython syntax with unittest.
2 2
3 3 In IPython up until 0.10, we've used very hacked up nose machinery for running
4 4 tests with IPython special syntax, and this has proved to be extremely slow.
5 5 This module provides decorators to try a different approach, stemming from a
6 6 conversation Brian and I (FP) had about this problem Sept/09.
7 7
8 8 The goal is to be able to easily write simple functions that can be seen by
9 9 unittest as tests, and ultimately for these to support doctests with full
10 10 IPython syntax. Nose already offers this based on naming conventions and our
11 11 hackish plugins, but we are seeking to move away from nose dependencies if
12 12 possible.
13 13
14 14 This module follows a different approach, based on decorators.
15 15
16 16 - A decorator called @ipdoctest can mark any function as having a docstring
17 17 that should be viewed as a doctest, but after syntax conversion.
18 18
19 19 Authors
20 20 -------
21 21
22 22 - Fernando Perez <Fernando.Perez@berkeley.edu>
23 23 """
24 24
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Copyright (C) 2009-2011 The IPython Development Team
28 28 #
29 29 # Distributed under the terms of the BSD License. The full license is in
30 30 # the file COPYING, distributed as part of this software.
31 31 #-----------------------------------------------------------------------------
32 32
33 33 #-----------------------------------------------------------------------------
34 34 # Imports
35 35 #-----------------------------------------------------------------------------
36 36
37 37 # Stdlib
38 38 import re
39 39 import unittest
40 40 from doctest import DocTestFinder, DocTestRunner, TestResults
41 from IPython.terminal.interactiveshell import InteractiveShell
41 42
42 43 #-----------------------------------------------------------------------------
43 44 # Classes and functions
44 45 #-----------------------------------------------------------------------------
45 46
46 47 def count_failures(runner):
47 48 """Count number of failures in a doctest runner.
48 49
49 50 Code modeled after the summarize() method in doctest.
50 51 """
51 52 return [TestResults(f, t) for f, t in runner._name2ft.values() if f > 0 ]
52 53
53 54
54 55 class IPython2PythonConverter(object):
55 56 """Convert IPython 'syntax' to valid Python.
56 57
57 58 Eventually this code may grow to be the full IPython syntax conversion
58 59 implementation, but for now it only does prompt conversion."""
59 60
60 61 def __init__(self):
61 62 self.rps1 = re.compile(r'In\ \[\d+\]: ')
62 63 self.rps2 = re.compile(r'\ \ \ \.\.\.+: ')
63 64 self.rout = re.compile(r'Out\[\d+\]: \s*?\n?')
64 65 self.pyps1 = '>>> '
65 66 self.pyps2 = '... '
66 67 self.rpyps1 = re.compile (r'(\s*%s)(.*)$' % self.pyps1)
67 68 self.rpyps2 = re.compile (r'(\s*%s)(.*)$' % self.pyps2)
68 69
69 70 def __call__(self, ds):
70 71 """Convert IPython prompts to python ones in a string."""
71 72 from . import globalipapp
72 73
73 74 pyps1 = '>>> '
74 75 pyps2 = '... '
75 76 pyout = ''
76 77
77 78 dnew = ds
78 79 dnew = self.rps1.sub(pyps1, dnew)
79 80 dnew = self.rps2.sub(pyps2, dnew)
80 81 dnew = self.rout.sub(pyout, dnew)
81 ip = globalipapp.get_ipython()
82 ip = InteractiveShell.instance()
82 83
83 84 # Convert input IPython source into valid Python.
84 85 out = []
85 86 newline = out.append
86 87 for line in dnew.splitlines():
87 88
88 89 mps1 = self.rpyps1.match(line)
89 90 if mps1 is not None:
90 91 prompt, text = mps1.groups()
91 92 newline(prompt+ip.prefilter(text, False))
92 93 continue
93 94
94 95 mps2 = self.rpyps2.match(line)
95 96 if mps2 is not None:
96 97 prompt, text = mps2.groups()
97 98 newline(prompt+ip.prefilter(text, True))
98 99 continue
99 100
100 101 newline(line)
101 102 newline('') # ensure a closing newline, needed by doctest
102 103 #print "PYSRC:", '\n'.join(out) # dbg
103 104 return '\n'.join(out)
104 105
105 106 #return dnew
106 107
107 108
108 109 class Doc2UnitTester(object):
109 110 """Class whose instances act as a decorator for docstring testing.
110 111
111 112 In practice we're only likely to need one instance ever, made below (though
112 113 no attempt is made at turning it into a singleton, there is no need for
113 114 that).
114 115 """
115 116 def __init__(self, verbose=False):
116 117 """New decorator.
117 118
118 119 Parameters
119 120 ----------
120 121
121 122 verbose : boolean, optional (False)
122 123 Passed to the doctest finder and runner to control verbosity.
123 124 """
124 125 self.verbose = verbose
125 126 # We can reuse the same finder for all instances
126 127 self.finder = DocTestFinder(verbose=verbose, recurse=False)
127 128
128 129 def __call__(self, func):
129 130 """Use as a decorator: doctest a function's docstring as a unittest.
130 131
131 132 This version runs normal doctests, but the idea is to make it later run
132 133 ipython syntax instead."""
133 134
134 135 # Capture the enclosing instance with a different name, so the new
135 136 # class below can see it without confusion regarding its own 'self'
136 137 # that will point to the test instance at runtime
137 138 d2u = self
138 139
139 140 # Rewrite the function's docstring to have python syntax
140 141 if func.__doc__ is not None:
141 142 func.__doc__ = ip2py(func.__doc__)
142 143
143 144 # Now, create a tester object that is a real unittest instance, so
144 145 # normal unittest machinery (or Nose, or Trial) can find it.
145 146 class Tester(unittest.TestCase):
146 147 def test(self):
147 148 # Make a new runner per function to be tested
148 149 runner = DocTestRunner(verbose=d2u.verbose)
149 150 for the_test in d2u.finder.find(func, func.__name__):
150 151 runner.run(the_test)
151 152 failed = count_failures(runner)
152 153 if failed:
153 154 # Since we only looked at a single function's docstring,
154 155 # failed should contain at most one item. More than that
155 156 # is a case we can't handle and should error out on
156 157 if len(failed) > 1:
157 158 err = "Invalid number of test results:" % failed
158 159 raise ValueError(err)
159 160 # Report a normal failure.
160 161 self.fail('failed doctests: %s' % str(failed[0]))
161 162
162 163 # Rename it so test reports have the original signature.
163 164 Tester.__name__ = func.__name__
164 165 return Tester
165 166
166 167
167 168 def ipdocstring(func):
168 169 """Change the function docstring via ip2py.
169 170 """
170 171 if func.__doc__ is not None:
171 172 func.__doc__ = ip2py(func.__doc__)
172 173 return func
173 174
174 175
175 176 # Make an instance of the classes for public use
176 177 ipdoctest = Doc2UnitTester()
177 178 ip2py = IPython2PythonConverter()
@@ -1,42 +1,43 b''
1 1 include README.rst
2 2 include COPYING.rst
3 3 include LICENSE
4 4 include setupbase.py
5 5 include setupegg.py
6 6 include MANIFEST.in
7 include pytest.ini
7 8 include .mailmap
8 9
9 10 recursive-exclude tools *
10 11 exclude tools
11 12 exclude CONTRIBUTING.md
12 13 exclude .editorconfig
13 14
14 15 graft setupext
15 16
16 17 graft scripts
17 18
18 19 # Load main dir but exclude things we don't want in the distro
19 20 graft IPython
20 21
21 22 # Documentation
22 23 graft docs
23 24 exclude docs/\#*
24 25 exclude docs/man/*.1.gz
25 26
26 27 # Examples
27 28 graft examples
28 29
29 30 # docs subdirs we want to skip
30 31 prune docs/build
31 32 prune docs/gh-pages
32 33 prune docs/dist
33 34
34 35 # Patterns to exclude from any directory
35 36 global-exclude *~
36 37 global-exclude *.flc
37 38 global-exclude *.yml
38 39 global-exclude *.pyc
39 40 global-exclude *.pyo
40 41 global-exclude .dircopy.log
41 42 global-exclude .git
42 43 global-exclude .ipynb_checkpoints
General Comments 0
You need to be logged in to leave comments. Login now