##// END OF EJS Templates
Remove iptest and other Nose-dependent code
Nikita Kniazev -
Show More
@@ -1,52 +1,47 b''
1 name: Run tests
1 name: Run tests
2
2
3 on:
3 on:
4 push:
4 push:
5 pull_request:
5 pull_request:
6 # Run weekly on Monday at 1:23 UTC
6 # Run weekly on Monday at 1:23 UTC
7 schedule:
7 schedule:
8 - cron: '23 1 * * 1'
8 - cron: '23 1 * * 1'
9 workflow_dispatch:
9 workflow_dispatch:
10
10
11
11
12 jobs:
12 jobs:
13 test:
13 test:
14 runs-on: ${{ matrix.os }}
14 runs-on: ${{ matrix.os }}
15 strategy:
15 strategy:
16 matrix:
16 matrix:
17 os: [ubuntu-latest]
17 os: [ubuntu-latest]
18 python-version: ["3.7", "3.8", "3.9"]
18 python-version: ["3.7", "3.8", "3.9"]
19 # Test all on ubuntu, test ends on macos
19 # Test all on ubuntu, test ends on macos
20 include:
20 include:
21 - os: macos-latest
21 - os: macos-latest
22 python-version: "3.7"
22 python-version: "3.7"
23 - os: macos-latest
23 - os: macos-latest
24 python-version: "3.9"
24 python-version: "3.9"
25
25
26 steps:
26 steps:
27 - uses: actions/checkout@v2
27 - uses: actions/checkout@v2
28 - name: Set up Python ${{ matrix.python-version }}
28 - name: Set up Python ${{ matrix.python-version }}
29 uses: actions/setup-python@v2
29 uses: actions/setup-python@v2
30 with:
30 with:
31 python-version: ${{ matrix.python-version }}
31 python-version: ${{ matrix.python-version }}
32 - name: Install and update Python dependencies
32 - name: Install and update Python dependencies
33 run: |
33 run: |
34 python -m pip install --upgrade pip setuptools wheel
34 python -m pip install --upgrade pip setuptools wheel
35 python -m pip install --upgrade -e file://$PWD#egg=ipython[test]
35 python -m pip install --upgrade -e file://$PWD#egg=ipython[test]
36 python -m pip install --upgrade --upgrade-strategy eager trio curio
36 python -m pip install --upgrade --upgrade-strategy eager trio curio
37 python -m pip install --upgrade pytest pytest-cov pytest-trio 'matplotlib!=3.2.0' pandas
37 python -m pip install --upgrade pytest pytest-cov pytest-trio 'matplotlib!=3.2.0' pandas
38 python -m pip install --upgrade check-manifest pytest-cov anyio
38 python -m pip install --upgrade check-manifest pytest-cov anyio
39 - name: Check manifest
39 - name: Check manifest
40 run: check-manifest
40 run: check-manifest
41 - name: iptest
42 run: |
43 cd /tmp && iptest --coverage xml && cd -
44 cp /tmp/ipy_coverage.xml ./
45 cp /tmp/.coverage ./
46 - name: pytest
41 - name: pytest
47 env:
42 env:
48 COLUMNS: 120
43 COLUMNS: 120
49 run: |
44 run: |
50 pytest --color=yes -v --cov --cov-report=xml
45 pytest --color=yes -v --cov --cov-report=xml
51 - name: Upload coverage to Codecov
46 - name: Upload coverage to Codecov
52 uses: codecov/codecov-action@v2
47 uses: codecov/codecov-action@v2
@@ -1,95 +1,90 b''
1 ## Triaging Issues
1 ## Triaging Issues
2
2
3 On the IPython repository, we strive to trust users and give them responsibility.
3 On the IPython repository, we strive to trust users and give them responsibility.
4 By using one of our bots, any user can close issues or add/remove
4 By using one of our bots, any user can close issues or add/remove
5 labels by mentioning the bot and asking it to do things on your behalf.
5 labels by mentioning the bot and asking it to do things on your behalf.
6
6
7 To close an issue (or PR), even if you did not create it, use the following:
7 To close an issue (or PR), even if you did not create it, use the following:
8
8
9 > @meeseeksdev close
9 > @meeseeksdev close
10
10
11 This command can be in the middle of another comment, but must start on its
11 This command can be in the middle of another comment, but must start on its
12 own line.
12 own line.
13
13
14 To add labels to an issue, ask the bot to `tag` with a comma-separated list of
14 To add labels to an issue, ask the bot to `tag` with a comma-separated list of
15 tags to add:
15 tags to add:
16
16
17 > @meeseeksdev tag windows, documentation
17 > @meeseeksdev tag windows, documentation
18
18
19 Only already pre-created tags can be added. So far, the list is limited to:
19 Only already pre-created tags can be added. So far, the list is limited to:
20 `async/await`, `backported`, `help wanted`, `documentation`, `notebook`,
20 `async/await`, `backported`, `help wanted`, `documentation`, `notebook`,
21 `tab-completion`, `windows`
21 `tab-completion`, `windows`
22
22
23 To remove a label, use the `untag` command:
23 To remove a label, use the `untag` command:
24
24
25 > @meeseeksdev untag windows, documentation
25 > @meeseeksdev untag windows, documentation
26
26
27 We'll be adding additional capabilities for the bot and will share them here
27 We'll be adding additional capabilities for the bot and will share them here
28 when they are ready to be used.
28 when they are ready to be used.
29
29
30 ## Opening an Issue
30 ## Opening an Issue
31
31
32 When opening a new Issue, please take the following steps:
32 When opening a new Issue, please take the following steps:
33
33
34 1. Search GitHub and/or Google for your issue to avoid duplicate reports.
34 1. Search GitHub and/or Google for your issue to avoid duplicate reports.
35 Keyword searches for your error messages are most helpful.
35 Keyword searches for your error messages are most helpful.
36 2. If possible, try updating to master and reproducing your issue,
36 2. If possible, try updating to master and reproducing your issue,
37 because we may have already fixed it.
37 because we may have already fixed it.
38 3. Try to include a minimal reproducible test case.
38 3. Try to include a minimal reproducible test case.
39 4. Include relevant system information. Start with the output of:
39 4. Include relevant system information. Start with the output of:
40
40
41 python -c "import IPython; print(IPython.sys_info())"
41 python -c "import IPython; print(IPython.sys_info())"
42
42
43 And include any relevant package versions, depending on the issue, such as
43 And include any relevant package versions, depending on the issue, such as
44 matplotlib, numpy, Qt, Qt bindings (PyQt/PySide), tornado, web browser, etc.
44 matplotlib, numpy, Qt, Qt bindings (PyQt/PySide), tornado, web browser, etc.
45
45
46 ## Pull Requests
46 ## Pull Requests
47
47
48 Some guidelines on contributing to IPython:
48 Some guidelines on contributing to IPython:
49
49
50 * All work is submitted via Pull Requests.
50 * All work is submitted via Pull Requests.
51 * Pull Requests can be submitted as soon as there is code worth discussing.
51 * Pull Requests can be submitted as soon as there is code worth discussing.
52 Pull Requests track the branch, so you can continue to work after the PR is submitted.
52 Pull Requests track the branch, so you can continue to work after the PR is submitted.
53 Review and discussion can begin well before the work is complete,
53 Review and discussion can begin well before the work is complete,
54 and the more discussion the better.
54 and the more discussion the better.
55 The worst case is that the PR is closed.
55 The worst case is that the PR is closed.
56 * Pull Requests should generally be made against master
56 * Pull Requests should generally be made against master
57 * Pull Requests should be tested, if feasible:
57 * Pull Requests should be tested, if feasible:
58 - bugfixes should include regression tests.
58 - bugfixes should include regression tests.
59 - new behavior should at least get minimal exercise.
59 - new behavior should at least get minimal exercise.
60 * New features and backwards-incompatible changes should be documented by adding
60 * New features and backwards-incompatible changes should be documented by adding
61 a new file to the [pr](docs/source/whatsnew/pr) directory, see [the README.md
61 a new file to the [pr](docs/source/whatsnew/pr) directory, see [the README.md
62 there](docs/source/whatsnew/pr/README.md) for details.
62 there](docs/source/whatsnew/pr/README.md) for details.
63 * Don't make 'cleanup' pull requests just to change code style.
63 * Don't make 'cleanup' pull requests just to change code style.
64 We don't follow any style guide strictly, and we consider formatting changes
64 We don't follow any style guide strictly, and we consider formatting changes
65 unnecessary noise.
65 unnecessary noise.
66 If you're making functional changes, you can clean up the specific pieces of
66 If you're making functional changes, you can clean up the specific pieces of
67 code you're working on.
67 code you're working on.
68
68
69 [Travis](http://travis-ci.org/#!/ipython/ipython) does a pretty good job testing
69 [Travis](http://travis-ci.org/#!/ipython/ipython) does a pretty good job testing
70 IPython and Pull Requests, but it may make sense to manually perform tests,
70 IPython and Pull Requests, but it may make sense to manually perform tests,
71 particularly for PRs that affect `IPython.parallel` or Windows.
71 particularly for PRs that affect `IPython.parallel` or Windows.
72
72
73 For more detailed information, see our [GitHub Workflow](https://github.com/ipython/ipython/wiki/Dev:-GitHub-workflow).
73 For more detailed information, see our [GitHub Workflow](https://github.com/ipython/ipython/wiki/Dev:-GitHub-workflow).
74
74
75 ## Running Tests
75 ## Running Tests
76
76
77 All the tests can be run by using
77 All the tests can be run by using
78 ```shell
78 ```shell
79 iptest
79 pytest
80 ```
80 ```
81
81
82 All the tests for a single module (for example **test_alias**) can be run by using the fully qualified path to the module.
82 All the tests for a single module (for example **test_alias**) can be run by using the fully qualified path to the module.
83 ```shell
83 ```shell
84 iptest IPython.core.tests.test_alias
84 pytest IPython/core/tests/test_alias.py
85 ```
85 ```
86
86
87 Only a single test (for example **test_alias_lifecycle**) within a single file can be run by adding the specific test after a `:` at the end:
87 Only a single test (for example **test_alias_lifecycle**) within a single file can be run by adding the specific test after a `::` at the end:
88 ```shell
88 ```shell
89 iptest IPython.core.tests.test_alias:test_alias_lifecycle
89 pytest IPython/core/tests/test_alias.py::test_alias_lifecycle
90 ```
91
92 For convenience, the full path to a file can often be used instead of the module path on unix systems. For example we can run all the tests by using
93 ```shell
94 iptest IPython/core/tests/test_alias.py
95 ```
90 ```
@@ -1,144 +1,143 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 IPython: tools for interactive and parallel computing in Python.
3 IPython: tools for interactive and parallel computing in Python.
4
4
5 https://ipython.org
5 https://ipython.org
6 """
6 """
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (c) 2008-2011, IPython Development Team.
8 # Copyright (c) 2008-2011, IPython Development Team.
9 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
9 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
10 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
10 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
11 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
11 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
12 #
12 #
13 # Distributed under the terms of the Modified BSD License.
13 # Distributed under the terms of the Modified BSD License.
14 #
14 #
15 # The full license is in the file COPYING.txt, distributed with this software.
15 # The full license is in the file COPYING.txt, distributed with this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 import os
22 import os
23 import sys
23 import sys
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Setup everything
26 # Setup everything
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28
28
29 # Don't forget to also update setup.py when this changes!
29 # Don't forget to also update setup.py when this changes!
30 if sys.version_info < (3, 6):
30 if sys.version_info < (3, 6):
31 raise ImportError(
31 raise ImportError(
32 """
32 """
33 IPython 7.10+ supports Python 3.6 and above.
33 IPython 7.10+ supports Python 3.6 and above.
34 When using Python 2.7, please install IPython 5.x LTS Long Term Support version.
34 When using Python 2.7, please install IPython 5.x LTS Long Term Support version.
35 Python 3.3 and 3.4 were supported up to IPython 6.x.
35 Python 3.3 and 3.4 were supported up to IPython 6.x.
36 Python 3.5 was supported with IPython 7.0 to 7.9.
36 Python 3.5 was supported with IPython 7.0 to 7.9.
37
37
38 See IPython `README.rst` file for more information:
38 See IPython `README.rst` file for more information:
39
39
40 https://github.com/ipython/ipython/blob/master/README.rst
40 https://github.com/ipython/ipython/blob/master/README.rst
41
41
42 """)
42 """)
43
43
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45 # Setup the top level names
45 # Setup the top level names
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47
47
48 from .core.getipython import get_ipython
48 from .core.getipython import get_ipython
49 from .core import release
49 from .core import release
50 from .core.application import Application
50 from .core.application import Application
51 from .terminal.embed import embed
51 from .terminal.embed import embed
52
52
53 from .core.interactiveshell import InteractiveShell
53 from .core.interactiveshell import InteractiveShell
54 from .testing import test
55 from .utils.sysinfo import sys_info
54 from .utils.sysinfo import sys_info
56 from .utils.frame import extract_module_locals
55 from .utils.frame import extract_module_locals
57
56
58 # Release data
57 # Release data
59 __author__ = '%s <%s>' % (release.author, release.author_email)
58 __author__ = '%s <%s>' % (release.author, release.author_email)
60 __license__ = release.license
59 __license__ = release.license
61 __version__ = release.version
60 __version__ = release.version
62 version_info = release.version_info
61 version_info = release.version_info
63
62
64 def embed_kernel(module=None, local_ns=None, **kwargs):
63 def embed_kernel(module=None, local_ns=None, **kwargs):
65 """Embed and start an IPython kernel in a given scope.
64 """Embed and start an IPython kernel in a given scope.
66
65
67 If you don't want the kernel to initialize the namespace
66 If you don't want the kernel to initialize the namespace
68 from the scope of the surrounding function,
67 from the scope of the surrounding function,
69 and/or you want to load full IPython configuration,
68 and/or you want to load full IPython configuration,
70 you probably want `IPython.start_kernel()` instead.
69 you probably want `IPython.start_kernel()` instead.
71
70
72 Parameters
71 Parameters
73 ----------
72 ----------
74 module : types.ModuleType, optional
73 module : types.ModuleType, optional
75 The module to load into IPython globals (default: caller)
74 The module to load into IPython globals (default: caller)
76 local_ns : dict, optional
75 local_ns : dict, optional
77 The namespace to load into IPython user namespace (default: caller)
76 The namespace to load into IPython user namespace (default: caller)
78 **kwargs : various, optional
77 **kwargs : various, optional
79 Further keyword args are relayed to the IPKernelApp constructor,
78 Further keyword args are relayed to the IPKernelApp constructor,
80 allowing configuration of the Kernel. Will only have an effect
79 allowing configuration of the Kernel. Will only have an effect
81 on the first embed_kernel call for a given process.
80 on the first embed_kernel call for a given process.
82 """
81 """
83
82
84 (caller_module, caller_locals) = extract_module_locals(1)
83 (caller_module, caller_locals) = extract_module_locals(1)
85 if module is None:
84 if module is None:
86 module = caller_module
85 module = caller_module
87 if local_ns is None:
86 if local_ns is None:
88 local_ns = caller_locals
87 local_ns = caller_locals
89
88
90 # Only import .zmq when we really need it
89 # Only import .zmq when we really need it
91 from ipykernel.embed import embed_kernel as real_embed_kernel
90 from ipykernel.embed import embed_kernel as real_embed_kernel
92 real_embed_kernel(module=module, local_ns=local_ns, **kwargs)
91 real_embed_kernel(module=module, local_ns=local_ns, **kwargs)
93
92
94 def start_ipython(argv=None, **kwargs):
93 def start_ipython(argv=None, **kwargs):
95 """Launch a normal IPython instance (as opposed to embedded)
94 """Launch a normal IPython instance (as opposed to embedded)
96
95
97 `IPython.embed()` puts a shell in a particular calling scope,
96 `IPython.embed()` puts a shell in a particular calling scope,
98 such as a function or method for debugging purposes,
97 such as a function or method for debugging purposes,
99 which is often not desirable.
98 which is often not desirable.
100
99
101 `start_ipython()` does full, regular IPython initialization,
100 `start_ipython()` does full, regular IPython initialization,
102 including loading startup files, configuration, etc.
101 including loading startup files, configuration, etc.
103 much of which is skipped by `embed()`.
102 much of which is skipped by `embed()`.
104
103
105 This is a public API method, and will survive implementation changes.
104 This is a public API method, and will survive implementation changes.
106
105
107 Parameters
106 Parameters
108 ----------
107 ----------
109 argv : list or None, optional
108 argv : list or None, optional
110 If unspecified or None, IPython will parse command-line options from sys.argv.
109 If unspecified or None, IPython will parse command-line options from sys.argv.
111 To prevent any command-line parsing, pass an empty list: `argv=[]`.
110 To prevent any command-line parsing, pass an empty list: `argv=[]`.
112 user_ns : dict, optional
111 user_ns : dict, optional
113 specify this dictionary to initialize the IPython user namespace with particular values.
112 specify this dictionary to initialize the IPython user namespace with particular values.
114 **kwargs : various, optional
113 **kwargs : various, optional
115 Any other kwargs will be passed to the Application constructor,
114 Any other kwargs will be passed to the Application constructor,
116 such as `config`.
115 such as `config`.
117 """
116 """
118 from IPython.terminal.ipapp import launch_new_instance
117 from IPython.terminal.ipapp import launch_new_instance
119 return launch_new_instance(argv=argv, **kwargs)
118 return launch_new_instance(argv=argv, **kwargs)
120
119
121 def start_kernel(argv=None, **kwargs):
120 def start_kernel(argv=None, **kwargs):
122 """Launch a normal IPython kernel instance (as opposed to embedded)
121 """Launch a normal IPython kernel instance (as opposed to embedded)
123
122
124 `IPython.embed_kernel()` puts a shell in a particular calling scope,
123 `IPython.embed_kernel()` puts a shell in a particular calling scope,
125 such as a function or method for debugging purposes,
124 such as a function or method for debugging purposes,
126 which is often not desirable.
125 which is often not desirable.
127
126
128 `start_kernel()` does full, regular IPython initialization,
127 `start_kernel()` does full, regular IPython initialization,
129 including loading startup files, configuration, etc.
128 including loading startup files, configuration, etc.
130 much of which is skipped by `embed()`.
129 much of which is skipped by `embed()`.
131
130
132 Parameters
131 Parameters
133 ----------
132 ----------
134 argv : list or None, optional
133 argv : list or None, optional
135 If unspecified or None, IPython will parse command-line options from sys.argv.
134 If unspecified or None, IPython will parse command-line options from sys.argv.
136 To prevent any command-line parsing, pass an empty list: `argv=[]`.
135 To prevent any command-line parsing, pass an empty list: `argv=[]`.
137 user_ns : dict, optional
136 user_ns : dict, optional
138 specify this dictionary to initialize the IPython user namespace with particular values.
137 specify this dictionary to initialize the IPython user namespace with particular values.
139 **kwargs : various, optional
138 **kwargs : various, optional
140 Any other kwargs will be passed to the Application constructor,
139 Any other kwargs will be passed to the Application constructor,
141 such as `config`.
140 such as `config`.
142 """
141 """
143 from IPython.kernel.zmq.kernelapp import launch_new_instance
142 from IPython.kernel.zmq.kernelapp import launch_new_instance
144 return launch_new_instance(argv=argv, **kwargs)
143 return launch_new_instance(argv=argv, **kwargs)
@@ -1,1284 +1,1284 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for the IPython tab-completion machinery."""
2 """Tests for the IPython tab-completion machinery."""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 import os
7 import os
8 import pytest
8 import sys
9 import sys
9 import textwrap
10 import textwrap
10 import unittest
11 import unittest
11
12
12 from contextlib import contextmanager
13 from contextlib import contextmanager
13
14
14 from traitlets.config.loader import Config
15 from traitlets.config.loader import Config
15 from IPython import get_ipython
16 from IPython import get_ipython
16 from IPython.core import completer
17 from IPython.core import completer
17 from IPython.external import decorators
18 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
18 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
19 from IPython.utils.generics import complete_object
19 from IPython.utils.generics import complete_object
20 from IPython.testing import decorators as dec
20 from IPython.testing import decorators as dec
21
21
22 from IPython.core.completer import (
22 from IPython.core.completer import (
23 Completion,
23 Completion,
24 provisionalcompleter,
24 provisionalcompleter,
25 match_dict_keys,
25 match_dict_keys,
26 _deduplicate_completions,
26 _deduplicate_completions,
27 )
27 )
28
28
29 # -----------------------------------------------------------------------------
29 # -----------------------------------------------------------------------------
30 # Test functions
30 # Test functions
31 # -----------------------------------------------------------------------------
31 # -----------------------------------------------------------------------------
32
32
33 def recompute_unicode_ranges():
33 def recompute_unicode_ranges():
34 """
34 """
35 utility to recompute the largest unicode range without any characters
35 utility to recompute the largest unicode range without any characters
36
36
37 use to recompute the gap in the global _UNICODE_RANGES of completer.py
37 use to recompute the gap in the global _UNICODE_RANGES of completer.py
38 """
38 """
39 import itertools
39 import itertools
40 import unicodedata
40 import unicodedata
41 valid = []
41 valid = []
42 for c in range(0,0x10FFFF + 1):
42 for c in range(0,0x10FFFF + 1):
43 try:
43 try:
44 unicodedata.name(chr(c))
44 unicodedata.name(chr(c))
45 except ValueError:
45 except ValueError:
46 continue
46 continue
47 valid.append(c)
47 valid.append(c)
48
48
49 def ranges(i):
49 def ranges(i):
50 for a, b in itertools.groupby(enumerate(i), lambda pair: pair[1] - pair[0]):
50 for a, b in itertools.groupby(enumerate(i), lambda pair: pair[1] - pair[0]):
51 b = list(b)
51 b = list(b)
52 yield b[0][1], b[-1][1]
52 yield b[0][1], b[-1][1]
53
53
54 rg = list(ranges(valid))
54 rg = list(ranges(valid))
55 lens = []
55 lens = []
56 gap_lens = []
56 gap_lens = []
57 pstart, pstop = 0,0
57 pstart, pstop = 0,0
58 for start, stop in rg:
58 for start, stop in rg:
59 lens.append(stop-start)
59 lens.append(stop-start)
60 gap_lens.append((start - pstop, hex(pstop), hex(start), f'{round((start - pstop)/0xe01f0*100)}%'))
60 gap_lens.append((start - pstop, hex(pstop), hex(start), f'{round((start - pstop)/0xe01f0*100)}%'))
61 pstart, pstop = start, stop
61 pstart, pstop = start, stop
62
62
63 return sorted(gap_lens)[-1]
63 return sorted(gap_lens)[-1]
64
64
65
65
66
66
67 def test_unicode_range():
67 def test_unicode_range():
68 """
68 """
69 Test that the ranges we test for unicode names give the same number of
69 Test that the ranges we test for unicode names give the same number of
70 results than testing the full length.
70 results than testing the full length.
71 """
71 """
72 from IPython.core.completer import _unicode_name_compute, _UNICODE_RANGES
72 from IPython.core.completer import _unicode_name_compute, _UNICODE_RANGES
73
73
74 expected_list = _unicode_name_compute([(0, 0x110000)])
74 expected_list = _unicode_name_compute([(0, 0x110000)])
75 test = _unicode_name_compute(_UNICODE_RANGES)
75 test = _unicode_name_compute(_UNICODE_RANGES)
76 len_exp = len(expected_list)
76 len_exp = len(expected_list)
77 len_test = len(test)
77 len_test = len(test)
78
78
79 # do not inline the len() or on error pytest will try to print the 130 000 +
79 # do not inline the len() or on error pytest will try to print the 130 000 +
80 # elements.
80 # elements.
81 message = None
81 message = None
82 if len_exp != len_test or len_exp > 131808:
82 if len_exp != len_test or len_exp > 131808:
83 size, start, stop, prct = recompute_unicode_ranges()
83 size, start, stop, prct = recompute_unicode_ranges()
84 message = f"""_UNICODE_RANGES likely wrong and need updating. This is
84 message = f"""_UNICODE_RANGES likely wrong and need updating. This is
85 likely due to a new release of Python. We've find that the biggest gap
85 likely due to a new release of Python. We've find that the biggest gap
86 in unicode characters has reduces in size to be {size} characters
86 in unicode characters has reduces in size to be {size} characters
87 ({prct}), from {start}, to {stop}. In completer.py likely update to
87 ({prct}), from {start}, to {stop}. In completer.py likely update to
88
88
89 _UNICODE_RANGES = [(32, {start}), ({stop}, 0xe01f0)]
89 _UNICODE_RANGES = [(32, {start}), ({stop}, 0xe01f0)]
90
90
91 And update the assertion below to use
91 And update the assertion below to use
92
92
93 len_exp <= {len_exp}
93 len_exp <= {len_exp}
94 """
94 """
95 assert len_exp == len_test, message
95 assert len_exp == len_test, message
96
96
97 # fail if new unicode symbols have been added.
97 # fail if new unicode symbols have been added.
98 assert len_exp <= 137714, message
98 assert len_exp <= 137714, message
99
99
100
100
101 @contextmanager
101 @contextmanager
102 def greedy_completion():
102 def greedy_completion():
103 ip = get_ipython()
103 ip = get_ipython()
104 greedy_original = ip.Completer.greedy
104 greedy_original = ip.Completer.greedy
105 try:
105 try:
106 ip.Completer.greedy = True
106 ip.Completer.greedy = True
107 yield
107 yield
108 finally:
108 finally:
109 ip.Completer.greedy = greedy_original
109 ip.Completer.greedy = greedy_original
110
110
111
111
112 def test_protect_filename():
112 def test_protect_filename():
113 if sys.platform == "win32":
113 if sys.platform == "win32":
114 pairs = [
114 pairs = [
115 ("abc", "abc"),
115 ("abc", "abc"),
116 (" abc", '" abc"'),
116 (" abc", '" abc"'),
117 ("a bc", '"a bc"'),
117 ("a bc", '"a bc"'),
118 ("a bc", '"a bc"'),
118 ("a bc", '"a bc"'),
119 (" bc", '" bc"'),
119 (" bc", '" bc"'),
120 ]
120 ]
121 else:
121 else:
122 pairs = [
122 pairs = [
123 ("abc", "abc"),
123 ("abc", "abc"),
124 (" abc", r"\ abc"),
124 (" abc", r"\ abc"),
125 ("a bc", r"a\ bc"),
125 ("a bc", r"a\ bc"),
126 ("a bc", r"a\ \ bc"),
126 ("a bc", r"a\ \ bc"),
127 (" bc", r"\ \ bc"),
127 (" bc", r"\ \ bc"),
128 # On posix, we also protect parens and other special characters.
128 # On posix, we also protect parens and other special characters.
129 ("a(bc", r"a\(bc"),
129 ("a(bc", r"a\(bc"),
130 ("a)bc", r"a\)bc"),
130 ("a)bc", r"a\)bc"),
131 ("a( )bc", r"a\(\ \)bc"),
131 ("a( )bc", r"a\(\ \)bc"),
132 ("a[1]bc", r"a\[1\]bc"),
132 ("a[1]bc", r"a\[1\]bc"),
133 ("a{1}bc", r"a\{1\}bc"),
133 ("a{1}bc", r"a\{1\}bc"),
134 ("a#bc", r"a\#bc"),
134 ("a#bc", r"a\#bc"),
135 ("a?bc", r"a\?bc"),
135 ("a?bc", r"a\?bc"),
136 ("a=bc", r"a\=bc"),
136 ("a=bc", r"a\=bc"),
137 ("a\\bc", r"a\\bc"),
137 ("a\\bc", r"a\\bc"),
138 ("a|bc", r"a\|bc"),
138 ("a|bc", r"a\|bc"),
139 ("a;bc", r"a\;bc"),
139 ("a;bc", r"a\;bc"),
140 ("a:bc", r"a\:bc"),
140 ("a:bc", r"a\:bc"),
141 ("a'bc", r"a\'bc"),
141 ("a'bc", r"a\'bc"),
142 ("a*bc", r"a\*bc"),
142 ("a*bc", r"a\*bc"),
143 ('a"bc', r"a\"bc"),
143 ('a"bc', r"a\"bc"),
144 ("a^bc", r"a\^bc"),
144 ("a^bc", r"a\^bc"),
145 ("a&bc", r"a\&bc"),
145 ("a&bc", r"a\&bc"),
146 ]
146 ]
147 # run the actual tests
147 # run the actual tests
148 for s1, s2 in pairs:
148 for s1, s2 in pairs:
149 s1p = completer.protect_filename(s1)
149 s1p = completer.protect_filename(s1)
150 assert s1p == s2
150 assert s1p == s2
151
151
152
152
153 def check_line_split(splitter, test_specs):
153 def check_line_split(splitter, test_specs):
154 for part1, part2, split in test_specs:
154 for part1, part2, split in test_specs:
155 cursor_pos = len(part1)
155 cursor_pos = len(part1)
156 line = part1 + part2
156 line = part1 + part2
157 out = splitter.split_line(line, cursor_pos)
157 out = splitter.split_line(line, cursor_pos)
158 assert out == split
158 assert out == split
159
159
160
160
161 def test_line_split():
161 def test_line_split():
162 """Basic line splitter test with default specs."""
162 """Basic line splitter test with default specs."""
163 sp = completer.CompletionSplitter()
163 sp = completer.CompletionSplitter()
164 # The format of the test specs is: part1, part2, expected answer. Parts 1
164 # The format of the test specs is: part1, part2, expected answer. Parts 1
165 # and 2 are joined into the 'line' sent to the splitter, as if the cursor
165 # and 2 are joined into the 'line' sent to the splitter, as if the cursor
166 # was at the end of part1. So an empty part2 represents someone hitting
166 # was at the end of part1. So an empty part2 represents someone hitting
167 # tab at the end of the line, the most common case.
167 # tab at the end of the line, the most common case.
168 t = [
168 t = [
169 ("run some/scrip", "", "some/scrip"),
169 ("run some/scrip", "", "some/scrip"),
170 ("run scripts/er", "ror.py foo", "scripts/er"),
170 ("run scripts/er", "ror.py foo", "scripts/er"),
171 ("echo $HOM", "", "HOM"),
171 ("echo $HOM", "", "HOM"),
172 ("print sys.pa", "", "sys.pa"),
172 ("print sys.pa", "", "sys.pa"),
173 ("print(sys.pa", "", "sys.pa"),
173 ("print(sys.pa", "", "sys.pa"),
174 ("execfile('scripts/er", "", "scripts/er"),
174 ("execfile('scripts/er", "", "scripts/er"),
175 ("a[x.", "", "x."),
175 ("a[x.", "", "x."),
176 ("a[x.", "y", "x."),
176 ("a[x.", "y", "x."),
177 ('cd "some_file/', "", "some_file/"),
177 ('cd "some_file/', "", "some_file/"),
178 ]
178 ]
179 check_line_split(sp, t)
179 check_line_split(sp, t)
180 # Ensure splitting works OK with unicode by re-running the tests with
180 # Ensure splitting works OK with unicode by re-running the tests with
181 # all inputs turned into unicode
181 # all inputs turned into unicode
182 check_line_split(sp, [map(str, p) for p in t])
182 check_line_split(sp, [map(str, p) for p in t])
183
183
184
184
185 class NamedInstanceMetaclass(type):
185 class NamedInstanceMetaclass(type):
186 def __getitem__(cls, item):
186 def __getitem__(cls, item):
187 return cls.get_instance(item)
187 return cls.get_instance(item)
188
188
189
189
190 class NamedInstanceClass(metaclass=NamedInstanceMetaclass):
190 class NamedInstanceClass(metaclass=NamedInstanceMetaclass):
191 def __init__(self, name):
191 def __init__(self, name):
192 if not hasattr(self.__class__, "instances"):
192 if not hasattr(self.__class__, "instances"):
193 self.__class__.instances = {}
193 self.__class__.instances = {}
194 self.__class__.instances[name] = self
194 self.__class__.instances[name] = self
195
195
196 @classmethod
196 @classmethod
197 def _ipython_key_completions_(cls):
197 def _ipython_key_completions_(cls):
198 return cls.instances.keys()
198 return cls.instances.keys()
199
199
200 @classmethod
200 @classmethod
201 def get_instance(cls, name):
201 def get_instance(cls, name):
202 return cls.instances[name]
202 return cls.instances[name]
203
203
204
204
205 class KeyCompletable:
205 class KeyCompletable:
206 def __init__(self, things=()):
206 def __init__(self, things=()):
207 self.things = things
207 self.things = things
208
208
209 def _ipython_key_completions_(self):
209 def _ipython_key_completions_(self):
210 return list(self.things)
210 return list(self.things)
211
211
212
212
213 class TestCompleter(unittest.TestCase):
213 class TestCompleter(unittest.TestCase):
214 def setUp(self):
214 def setUp(self):
215 """
215 """
216 We want to silence all PendingDeprecationWarning when testing the completer
216 We want to silence all PendingDeprecationWarning when testing the completer
217 """
217 """
218 self._assertwarns = self.assertWarns(PendingDeprecationWarning)
218 self._assertwarns = self.assertWarns(PendingDeprecationWarning)
219 self._assertwarns.__enter__()
219 self._assertwarns.__enter__()
220
220
221 def tearDown(self):
221 def tearDown(self):
222 try:
222 try:
223 self._assertwarns.__exit__(None, None, None)
223 self._assertwarns.__exit__(None, None, None)
224 except AssertionError:
224 except AssertionError:
225 pass
225 pass
226
226
227 def test_custom_completion_error(self):
227 def test_custom_completion_error(self):
228 """Test that errors from custom attribute completers are silenced."""
228 """Test that errors from custom attribute completers are silenced."""
229 ip = get_ipython()
229 ip = get_ipython()
230
230
231 class A:
231 class A:
232 pass
232 pass
233
233
234 ip.user_ns["x"] = A()
234 ip.user_ns["x"] = A()
235
235
236 @complete_object.register(A)
236 @complete_object.register(A)
237 def complete_A(a, existing_completions):
237 def complete_A(a, existing_completions):
238 raise TypeError("this should be silenced")
238 raise TypeError("this should be silenced")
239
239
240 ip.complete("x.")
240 ip.complete("x.")
241
241
242 def test_custom_completion_ordering(self):
242 def test_custom_completion_ordering(self):
243 """Test that errors from custom attribute completers are silenced."""
243 """Test that errors from custom attribute completers are silenced."""
244 ip = get_ipython()
244 ip = get_ipython()
245
245
246 _, matches = ip.complete('in')
246 _, matches = ip.complete('in')
247 assert matches.index('input') < matches.index('int')
247 assert matches.index('input') < matches.index('int')
248
248
249 def complete_example(a):
249 def complete_example(a):
250 return ['example2', 'example1']
250 return ['example2', 'example1']
251
251
252 ip.Completer.custom_completers.add_re('ex*', complete_example)
252 ip.Completer.custom_completers.add_re('ex*', complete_example)
253 _, matches = ip.complete('ex')
253 _, matches = ip.complete('ex')
254 assert matches.index('example2') < matches.index('example1')
254 assert matches.index('example2') < matches.index('example1')
255
255
256 def test_unicode_completions(self):
256 def test_unicode_completions(self):
257 ip = get_ipython()
257 ip = get_ipython()
258 # Some strings that trigger different types of completion. Check them both
258 # Some strings that trigger different types of completion. Check them both
259 # in str and unicode forms
259 # in str and unicode forms
260 s = ["ru", "%ru", "cd /", "floa", "float(x)/"]
260 s = ["ru", "%ru", "cd /", "floa", "float(x)/"]
261 for t in s + list(map(str, s)):
261 for t in s + list(map(str, s)):
262 # We don't need to check exact completion values (they may change
262 # We don't need to check exact completion values (they may change
263 # depending on the state of the namespace, but at least no exceptions
263 # depending on the state of the namespace, but at least no exceptions
264 # should be thrown and the return value should be a pair of text, list
264 # should be thrown and the return value should be a pair of text, list
265 # values.
265 # values.
266 text, matches = ip.complete(t)
266 text, matches = ip.complete(t)
267 self.assertIsInstance(text, str)
267 self.assertIsInstance(text, str)
268 self.assertIsInstance(matches, list)
268 self.assertIsInstance(matches, list)
269
269
270 def test_latex_completions(self):
270 def test_latex_completions(self):
271 from IPython.core.latex_symbols import latex_symbols
271 from IPython.core.latex_symbols import latex_symbols
272 import random
272 import random
273
273
274 ip = get_ipython()
274 ip = get_ipython()
275 # Test some random unicode symbols
275 # Test some random unicode symbols
276 keys = random.sample(latex_symbols.keys(), 10)
276 keys = random.sample(latex_symbols.keys(), 10)
277 for k in keys:
277 for k in keys:
278 text, matches = ip.complete(k)
278 text, matches = ip.complete(k)
279 self.assertEqual(text, k)
279 self.assertEqual(text, k)
280 self.assertEqual(matches, [latex_symbols[k]])
280 self.assertEqual(matches, [latex_symbols[k]])
281 # Test a more complex line
281 # Test a more complex line
282 text, matches = ip.complete("print(\\alpha")
282 text, matches = ip.complete("print(\\alpha")
283 self.assertEqual(text, "\\alpha")
283 self.assertEqual(text, "\\alpha")
284 self.assertEqual(matches[0], latex_symbols["\\alpha"])
284 self.assertEqual(matches[0], latex_symbols["\\alpha"])
285 # Test multiple matching latex symbols
285 # Test multiple matching latex symbols
286 text, matches = ip.complete("\\al")
286 text, matches = ip.complete("\\al")
287 self.assertIn("\\alpha", matches)
287 self.assertIn("\\alpha", matches)
288 self.assertIn("\\aleph", matches)
288 self.assertIn("\\aleph", matches)
289
289
290 def test_latex_no_results(self):
290 def test_latex_no_results(self):
291 """
291 """
292 forward latex should really return nothing in either field if nothing is found.
292 forward latex should really return nothing in either field if nothing is found.
293 """
293 """
294 ip = get_ipython()
294 ip = get_ipython()
295 text, matches = ip.Completer.latex_matches("\\really_i_should_match_nothing")
295 text, matches = ip.Completer.latex_matches("\\really_i_should_match_nothing")
296 self.assertEqual(text, "")
296 self.assertEqual(text, "")
297 self.assertEqual(matches, ())
297 self.assertEqual(matches, ())
298
298
299 def test_back_latex_completion(self):
299 def test_back_latex_completion(self):
300 ip = get_ipython()
300 ip = get_ipython()
301
301
302 # do not return more than 1 matches for \beta, only the latex one.
302 # do not return more than 1 matches for \beta, only the latex one.
303 name, matches = ip.complete("\\β")
303 name, matches = ip.complete("\\β")
304 self.assertEqual(matches, ["\\beta"])
304 self.assertEqual(matches, ["\\beta"])
305
305
306 def test_back_unicode_completion(self):
306 def test_back_unicode_completion(self):
307 ip = get_ipython()
307 ip = get_ipython()
308
308
309 name, matches = ip.complete("\\Ⅴ")
309 name, matches = ip.complete("\\Ⅴ")
310 self.assertEqual(matches, ("\\ROMAN NUMERAL FIVE",))
310 self.assertEqual(matches, ("\\ROMAN NUMERAL FIVE",))
311
311
312 def test_forward_unicode_completion(self):
312 def test_forward_unicode_completion(self):
313 ip = get_ipython()
313 ip = get_ipython()
314
314
315 name, matches = ip.complete("\\ROMAN NUMERAL FIVE")
315 name, matches = ip.complete("\\ROMAN NUMERAL FIVE")
316 self.assertEqual(matches, ["Ⅴ"]) # This is not a V
316 self.assertEqual(matches, ["Ⅴ"]) # This is not a V
317 self.assertEqual(matches, ["\u2164"]) # same as above but explicit.
317 self.assertEqual(matches, ["\u2164"]) # same as above but explicit.
318
318
319 @unittest.skip("now we have a completion for \jmath")
319 @unittest.skip("now we have a completion for \jmath")
320 @decorators.knownfailureif(
320 @pytest.mark.xfail(
321 sys.platform == "win32", "Fails if there is a C:\\j... path"
321 sys.platform == "win32", reason="Fails if there is a C:\\j... path"
322 )
322 )
323 def test_no_ascii_back_completion(self):
323 def test_no_ascii_back_completion(self):
324 ip = get_ipython()
324 ip = get_ipython()
325 with TemporaryWorkingDirectory(): # Avoid any filename completions
325 with TemporaryWorkingDirectory(): # Avoid any filename completions
326 # single ascii letter that don't have yet completions
326 # single ascii letter that don't have yet completions
327 for letter in "jJ":
327 for letter in "jJ":
328 name, matches = ip.complete("\\" + letter)
328 name, matches = ip.complete("\\" + letter)
329 self.assertEqual(matches, [])
329 self.assertEqual(matches, [])
330
330
331 def test_delim_setting(self):
331 def test_delim_setting(self):
332 sp = completer.CompletionSplitter()
332 sp = completer.CompletionSplitter()
333 sp.delims = " "
333 sp.delims = " "
334 self.assertEqual(sp.delims, " ")
334 self.assertEqual(sp.delims, " ")
335 self.assertEqual(sp._delim_expr, r"[\ ]")
335 self.assertEqual(sp._delim_expr, r"[\ ]")
336
336
337 def test_spaces(self):
337 def test_spaces(self):
338 """Test with only spaces as split chars."""
338 """Test with only spaces as split chars."""
339 sp = completer.CompletionSplitter()
339 sp = completer.CompletionSplitter()
340 sp.delims = " "
340 sp.delims = " "
341 t = [("foo", "", "foo"), ("run foo", "", "foo"), ("run foo", "bar", "foo")]
341 t = [("foo", "", "foo"), ("run foo", "", "foo"), ("run foo", "bar", "foo")]
342 check_line_split(sp, t)
342 check_line_split(sp, t)
343
343
344 def test_has_open_quotes1(self):
344 def test_has_open_quotes1(self):
345 for s in ["'", "'''", "'hi' '"]:
345 for s in ["'", "'''", "'hi' '"]:
346 self.assertEqual(completer.has_open_quotes(s), "'")
346 self.assertEqual(completer.has_open_quotes(s), "'")
347
347
348 def test_has_open_quotes2(self):
348 def test_has_open_quotes2(self):
349 for s in ['"', '"""', '"hi" "']:
349 for s in ['"', '"""', '"hi" "']:
350 self.assertEqual(completer.has_open_quotes(s), '"')
350 self.assertEqual(completer.has_open_quotes(s), '"')
351
351
352 def test_has_open_quotes3(self):
352 def test_has_open_quotes3(self):
353 for s in ["''", "''' '''", "'hi' 'ipython'"]:
353 for s in ["''", "''' '''", "'hi' 'ipython'"]:
354 self.assertFalse(completer.has_open_quotes(s))
354 self.assertFalse(completer.has_open_quotes(s))
355
355
356 def test_has_open_quotes4(self):
356 def test_has_open_quotes4(self):
357 for s in ['""', '""" """', '"hi" "ipython"']:
357 for s in ['""', '""" """', '"hi" "ipython"']:
358 self.assertFalse(completer.has_open_quotes(s))
358 self.assertFalse(completer.has_open_quotes(s))
359
359
360 @decorators.knownfailureif(
360 @pytest.mark.xfail(
361 sys.platform == "win32", "abspath completions fail on Windows"
361 sys.platform == "win32", reason="abspath completions fail on Windows"
362 )
362 )
363 def test_abspath_file_completions(self):
363 def test_abspath_file_completions(self):
364 ip = get_ipython()
364 ip = get_ipython()
365 with TemporaryDirectory() as tmpdir:
365 with TemporaryDirectory() as tmpdir:
366 prefix = os.path.join(tmpdir, "foo")
366 prefix = os.path.join(tmpdir, "foo")
367 suffixes = ["1", "2"]
367 suffixes = ["1", "2"]
368 names = [prefix + s for s in suffixes]
368 names = [prefix + s for s in suffixes]
369 for n in names:
369 for n in names:
370 open(n, "w").close()
370 open(n, "w").close()
371
371
372 # Check simple completion
372 # Check simple completion
373 c = ip.complete(prefix)[1]
373 c = ip.complete(prefix)[1]
374 self.assertEqual(c, names)
374 self.assertEqual(c, names)
375
375
376 # Now check with a function call
376 # Now check with a function call
377 cmd = 'a = f("%s' % prefix
377 cmd = 'a = f("%s' % prefix
378 c = ip.complete(prefix, cmd)[1]
378 c = ip.complete(prefix, cmd)[1]
379 comp = [prefix + s for s in suffixes]
379 comp = [prefix + s for s in suffixes]
380 self.assertEqual(c, comp)
380 self.assertEqual(c, comp)
381
381
382 def test_local_file_completions(self):
382 def test_local_file_completions(self):
383 ip = get_ipython()
383 ip = get_ipython()
384 with TemporaryWorkingDirectory():
384 with TemporaryWorkingDirectory():
385 prefix = "./foo"
385 prefix = "./foo"
386 suffixes = ["1", "2"]
386 suffixes = ["1", "2"]
387 names = [prefix + s for s in suffixes]
387 names = [prefix + s for s in suffixes]
388 for n in names:
388 for n in names:
389 open(n, "w").close()
389 open(n, "w").close()
390
390
391 # Check simple completion
391 # Check simple completion
392 c = ip.complete(prefix)[1]
392 c = ip.complete(prefix)[1]
393 self.assertEqual(c, names)
393 self.assertEqual(c, names)
394
394
395 # Now check with a function call
395 # Now check with a function call
396 cmd = 'a = f("%s' % prefix
396 cmd = 'a = f("%s' % prefix
397 c = ip.complete(prefix, cmd)[1]
397 c = ip.complete(prefix, cmd)[1]
398 comp = {prefix + s for s in suffixes}
398 comp = {prefix + s for s in suffixes}
399 self.assertTrue(comp.issubset(set(c)))
399 self.assertTrue(comp.issubset(set(c)))
400
400
401 def test_quoted_file_completions(self):
401 def test_quoted_file_completions(self):
402 ip = get_ipython()
402 ip = get_ipython()
403 with TemporaryWorkingDirectory():
403 with TemporaryWorkingDirectory():
404 name = "foo'bar"
404 name = "foo'bar"
405 open(name, "w").close()
405 open(name, "w").close()
406
406
407 # Don't escape Windows
407 # Don't escape Windows
408 escaped = name if sys.platform == "win32" else "foo\\'bar"
408 escaped = name if sys.platform == "win32" else "foo\\'bar"
409
409
410 # Single quote matches embedded single quote
410 # Single quote matches embedded single quote
411 text = "open('foo"
411 text = "open('foo"
412 c = ip.Completer._complete(
412 c = ip.Completer._complete(
413 cursor_line=0, cursor_pos=len(text), full_text=text
413 cursor_line=0, cursor_pos=len(text), full_text=text
414 )[1]
414 )[1]
415 self.assertEqual(c, [escaped])
415 self.assertEqual(c, [escaped])
416
416
417 # Double quote requires no escape
417 # Double quote requires no escape
418 text = 'open("foo'
418 text = 'open("foo'
419 c = ip.Completer._complete(
419 c = ip.Completer._complete(
420 cursor_line=0, cursor_pos=len(text), full_text=text
420 cursor_line=0, cursor_pos=len(text), full_text=text
421 )[1]
421 )[1]
422 self.assertEqual(c, [name])
422 self.assertEqual(c, [name])
423
423
424 # No quote requires an escape
424 # No quote requires an escape
425 text = "%ls foo"
425 text = "%ls foo"
426 c = ip.Completer._complete(
426 c = ip.Completer._complete(
427 cursor_line=0, cursor_pos=len(text), full_text=text
427 cursor_line=0, cursor_pos=len(text), full_text=text
428 )[1]
428 )[1]
429 self.assertEqual(c, [escaped])
429 self.assertEqual(c, [escaped])
430
430
431 def test_all_completions_dups(self):
431 def test_all_completions_dups(self):
432 """
432 """
433 Make sure the output of `IPCompleter.all_completions` does not have
433 Make sure the output of `IPCompleter.all_completions` does not have
434 duplicated prefixes.
434 duplicated prefixes.
435 """
435 """
436 ip = get_ipython()
436 ip = get_ipython()
437 c = ip.Completer
437 c = ip.Completer
438 ip.ex("class TestClass():\n\ta=1\n\ta1=2")
438 ip.ex("class TestClass():\n\ta=1\n\ta1=2")
439 for jedi_status in [True, False]:
439 for jedi_status in [True, False]:
440 with provisionalcompleter():
440 with provisionalcompleter():
441 ip.Completer.use_jedi = jedi_status
441 ip.Completer.use_jedi = jedi_status
442 matches = c.all_completions("TestCl")
442 matches = c.all_completions("TestCl")
443 assert matches == ['TestClass'], jedi_status
443 assert matches == ['TestClass'], jedi_status
444 matches = c.all_completions("TestClass.")
444 matches = c.all_completions("TestClass.")
445 assert len(matches) > 2, jedi_status
445 assert len(matches) > 2, jedi_status
446 matches = c.all_completions("TestClass.a")
446 matches = c.all_completions("TestClass.a")
447 assert matches == ['TestClass.a', 'TestClass.a1'], jedi_status
447 assert matches == ['TestClass.a', 'TestClass.a1'], jedi_status
448
448
449 def test_jedi(self):
449 def test_jedi(self):
450 """
450 """
451 A couple of issue we had with Jedi
451 A couple of issue we had with Jedi
452 """
452 """
453 ip = get_ipython()
453 ip = get_ipython()
454
454
455 def _test_complete(reason, s, comp, start=None, end=None):
455 def _test_complete(reason, s, comp, start=None, end=None):
456 l = len(s)
456 l = len(s)
457 start = start if start is not None else l
457 start = start if start is not None else l
458 end = end if end is not None else l
458 end = end if end is not None else l
459 with provisionalcompleter():
459 with provisionalcompleter():
460 ip.Completer.use_jedi = True
460 ip.Completer.use_jedi = True
461 completions = set(ip.Completer.completions(s, l))
461 completions = set(ip.Completer.completions(s, l))
462 ip.Completer.use_jedi = False
462 ip.Completer.use_jedi = False
463 assert Completion(start, end, comp) in completions, reason
463 assert Completion(start, end, comp) in completions, reason
464
464
465 def _test_not_complete(reason, s, comp):
465 def _test_not_complete(reason, s, comp):
466 l = len(s)
466 l = len(s)
467 with provisionalcompleter():
467 with provisionalcompleter():
468 ip.Completer.use_jedi = True
468 ip.Completer.use_jedi = True
469 completions = set(ip.Completer.completions(s, l))
469 completions = set(ip.Completer.completions(s, l))
470 ip.Completer.use_jedi = False
470 ip.Completer.use_jedi = False
471 assert Completion(l, l, comp) not in completions, reason
471 assert Completion(l, l, comp) not in completions, reason
472
472
473 import jedi
473 import jedi
474
474
475 jedi_version = tuple(int(i) for i in jedi.__version__.split(".")[:3])
475 jedi_version = tuple(int(i) for i in jedi.__version__.split(".")[:3])
476 if jedi_version > (0, 10):
476 if jedi_version > (0, 10):
477 _test_complete("jedi >0.9 should complete and not crash", "a=1;a.", "real")
477 _test_complete("jedi >0.9 should complete and not crash", "a=1;a.", "real")
478 _test_complete("can infer first argument", 'a=(1,"foo");a[0].', "real")
478 _test_complete("can infer first argument", 'a=(1,"foo");a[0].', "real")
479 _test_complete("can infer second argument", 'a=(1,"foo");a[1].', "capitalize")
479 _test_complete("can infer second argument", 'a=(1,"foo");a[1].', "capitalize")
480 _test_complete("cover duplicate completions", "im", "import", 0, 2)
480 _test_complete("cover duplicate completions", "im", "import", 0, 2)
481
481
482 _test_not_complete("does not mix types", 'a=(1,"foo");a[0].', "capitalize")
482 _test_not_complete("does not mix types", 'a=(1,"foo");a[0].', "capitalize")
483
483
484 def test_completion_have_signature(self):
484 def test_completion_have_signature(self):
485 """
485 """
486 Lets make sure jedi is capable of pulling out the signature of the function we are completing.
486 Lets make sure jedi is capable of pulling out the signature of the function we are completing.
487 """
487 """
488 ip = get_ipython()
488 ip = get_ipython()
489 with provisionalcompleter():
489 with provisionalcompleter():
490 ip.Completer.use_jedi = True
490 ip.Completer.use_jedi = True
491 completions = ip.Completer.completions("ope", 3)
491 completions = ip.Completer.completions("ope", 3)
492 c = next(completions) # should be `open`
492 c = next(completions) # should be `open`
493 ip.Completer.use_jedi = False
493 ip.Completer.use_jedi = False
494 assert "file" in c.signature, "Signature of function was not found by completer"
494 assert "file" in c.signature, "Signature of function was not found by completer"
495 assert (
495 assert (
496 "encoding" in c.signature
496 "encoding" in c.signature
497 ), "Signature of function was not found by completer"
497 ), "Signature of function was not found by completer"
498
498
499 def test_deduplicate_completions(self):
499 def test_deduplicate_completions(self):
500 """
500 """
501 Test that completions are correctly deduplicated (even if ranges are not the same)
501 Test that completions are correctly deduplicated (even if ranges are not the same)
502 """
502 """
503 ip = get_ipython()
503 ip = get_ipython()
504 ip.ex(
504 ip.ex(
505 textwrap.dedent(
505 textwrap.dedent(
506 """
506 """
507 class Z:
507 class Z:
508 zoo = 1
508 zoo = 1
509 """
509 """
510 )
510 )
511 )
511 )
512 with provisionalcompleter():
512 with provisionalcompleter():
513 ip.Completer.use_jedi = True
513 ip.Completer.use_jedi = True
514 l = list(
514 l = list(
515 _deduplicate_completions("Z.z", ip.Completer.completions("Z.z", 3))
515 _deduplicate_completions("Z.z", ip.Completer.completions("Z.z", 3))
516 )
516 )
517 ip.Completer.use_jedi = False
517 ip.Completer.use_jedi = False
518
518
519 assert len(l) == 1, "Completions (Z.z<tab>) correctly deduplicate: %s " % l
519 assert len(l) == 1, "Completions (Z.z<tab>) correctly deduplicate: %s " % l
520 assert l[0].text == "zoo" # and not `it.accumulate`
520 assert l[0].text == "zoo" # and not `it.accumulate`
521
521
522 def test_greedy_completions(self):
522 def test_greedy_completions(self):
523 """
523 """
524 Test the capability of the Greedy completer.
524 Test the capability of the Greedy completer.
525
525
526 Most of the test here does not really show off the greedy completer, for proof
526 Most of the test here does not really show off the greedy completer, for proof
527 each of the text below now pass with Jedi. The greedy completer is capable of more.
527 each of the text below now pass with Jedi. The greedy completer is capable of more.
528
528
529 See the :any:`test_dict_key_completion_contexts`
529 See the :any:`test_dict_key_completion_contexts`
530
530
531 """
531 """
532 ip = get_ipython()
532 ip = get_ipython()
533 ip.ex("a=list(range(5))")
533 ip.ex("a=list(range(5))")
534 _, c = ip.complete(".", line="a[0].")
534 _, c = ip.complete(".", line="a[0].")
535 self.assertFalse(".real" in c, "Shouldn't have completed on a[0]: %s" % c)
535 self.assertFalse(".real" in c, "Shouldn't have completed on a[0]: %s" % c)
536
536
537 def _(line, cursor_pos, expect, message, completion):
537 def _(line, cursor_pos, expect, message, completion):
538 with greedy_completion(), provisionalcompleter():
538 with greedy_completion(), provisionalcompleter():
539 ip.Completer.use_jedi = False
539 ip.Completer.use_jedi = False
540 _, c = ip.complete(".", line=line, cursor_pos=cursor_pos)
540 _, c = ip.complete(".", line=line, cursor_pos=cursor_pos)
541 self.assertIn(expect, c, message % c)
541 self.assertIn(expect, c, message % c)
542
542
543 ip.Completer.use_jedi = True
543 ip.Completer.use_jedi = True
544 with provisionalcompleter():
544 with provisionalcompleter():
545 completions = ip.Completer.completions(line, cursor_pos)
545 completions = ip.Completer.completions(line, cursor_pos)
546 self.assertIn(completion, completions)
546 self.assertIn(completion, completions)
547
547
548 with provisionalcompleter():
548 with provisionalcompleter():
549 _(
549 _(
550 "a[0].",
550 "a[0].",
551 5,
551 5,
552 "a[0].real",
552 "a[0].real",
553 "Should have completed on a[0].: %s",
553 "Should have completed on a[0].: %s",
554 Completion(5, 5, "real"),
554 Completion(5, 5, "real"),
555 )
555 )
556 _(
556 _(
557 "a[0].r",
557 "a[0].r",
558 6,
558 6,
559 "a[0].real",
559 "a[0].real",
560 "Should have completed on a[0].r: %s",
560 "Should have completed on a[0].r: %s",
561 Completion(5, 6, "real"),
561 Completion(5, 6, "real"),
562 )
562 )
563
563
564 _(
564 _(
565 "a[0].from_",
565 "a[0].from_",
566 10,
566 10,
567 "a[0].from_bytes",
567 "a[0].from_bytes",
568 "Should have completed on a[0].from_: %s",
568 "Should have completed on a[0].from_: %s",
569 Completion(5, 10, "from_bytes"),
569 Completion(5, 10, "from_bytes"),
570 )
570 )
571
571
572 def test_omit__names(self):
572 def test_omit__names(self):
573 # also happens to test IPCompleter as a configurable
573 # also happens to test IPCompleter as a configurable
574 ip = get_ipython()
574 ip = get_ipython()
575 ip._hidden_attr = 1
575 ip._hidden_attr = 1
576 ip._x = {}
576 ip._x = {}
577 c = ip.Completer
577 c = ip.Completer
578 ip.ex("ip=get_ipython()")
578 ip.ex("ip=get_ipython()")
579 cfg = Config()
579 cfg = Config()
580 cfg.IPCompleter.omit__names = 0
580 cfg.IPCompleter.omit__names = 0
581 c.update_config(cfg)
581 c.update_config(cfg)
582 with provisionalcompleter():
582 with provisionalcompleter():
583 c.use_jedi = False
583 c.use_jedi = False
584 s, matches = c.complete("ip.")
584 s, matches = c.complete("ip.")
585 self.assertIn("ip.__str__", matches)
585 self.assertIn("ip.__str__", matches)
586 self.assertIn("ip._hidden_attr", matches)
586 self.assertIn("ip._hidden_attr", matches)
587
587
588 # c.use_jedi = True
588 # c.use_jedi = True
589 # completions = set(c.completions('ip.', 3))
589 # completions = set(c.completions('ip.', 3))
590 # self.assertIn(Completion(3, 3, '__str__'), completions)
590 # self.assertIn(Completion(3, 3, '__str__'), completions)
591 # self.assertIn(Completion(3,3, "_hidden_attr"), completions)
591 # self.assertIn(Completion(3,3, "_hidden_attr"), completions)
592
592
593 cfg = Config()
593 cfg = Config()
594 cfg.IPCompleter.omit__names = 1
594 cfg.IPCompleter.omit__names = 1
595 c.update_config(cfg)
595 c.update_config(cfg)
596 with provisionalcompleter():
596 with provisionalcompleter():
597 c.use_jedi = False
597 c.use_jedi = False
598 s, matches = c.complete("ip.")
598 s, matches = c.complete("ip.")
599 self.assertNotIn("ip.__str__", matches)
599 self.assertNotIn("ip.__str__", matches)
600 # self.assertIn('ip._hidden_attr', matches)
600 # self.assertIn('ip._hidden_attr', matches)
601
601
602 # c.use_jedi = True
602 # c.use_jedi = True
603 # completions = set(c.completions('ip.', 3))
603 # completions = set(c.completions('ip.', 3))
604 # self.assertNotIn(Completion(3,3,'__str__'), completions)
604 # self.assertNotIn(Completion(3,3,'__str__'), completions)
605 # self.assertIn(Completion(3,3, "_hidden_attr"), completions)
605 # self.assertIn(Completion(3,3, "_hidden_attr"), completions)
606
606
607 cfg = Config()
607 cfg = Config()
608 cfg.IPCompleter.omit__names = 2
608 cfg.IPCompleter.omit__names = 2
609 c.update_config(cfg)
609 c.update_config(cfg)
610 with provisionalcompleter():
610 with provisionalcompleter():
611 c.use_jedi = False
611 c.use_jedi = False
612 s, matches = c.complete("ip.")
612 s, matches = c.complete("ip.")
613 self.assertNotIn("ip.__str__", matches)
613 self.assertNotIn("ip.__str__", matches)
614 self.assertNotIn("ip._hidden_attr", matches)
614 self.assertNotIn("ip._hidden_attr", matches)
615
615
616 # c.use_jedi = True
616 # c.use_jedi = True
617 # completions = set(c.completions('ip.', 3))
617 # completions = set(c.completions('ip.', 3))
618 # self.assertNotIn(Completion(3,3,'__str__'), completions)
618 # self.assertNotIn(Completion(3,3,'__str__'), completions)
619 # self.assertNotIn(Completion(3,3, "_hidden_attr"), completions)
619 # self.assertNotIn(Completion(3,3, "_hidden_attr"), completions)
620
620
621 with provisionalcompleter():
621 with provisionalcompleter():
622 c.use_jedi = False
622 c.use_jedi = False
623 s, matches = c.complete("ip._x.")
623 s, matches = c.complete("ip._x.")
624 self.assertIn("ip._x.keys", matches)
624 self.assertIn("ip._x.keys", matches)
625
625
626 # c.use_jedi = True
626 # c.use_jedi = True
627 # completions = set(c.completions('ip._x.', 6))
627 # completions = set(c.completions('ip._x.', 6))
628 # self.assertIn(Completion(6,6, "keys"), completions)
628 # self.assertIn(Completion(6,6, "keys"), completions)
629
629
630 del ip._hidden_attr
630 del ip._hidden_attr
631 del ip._x
631 del ip._x
632
632
633 def test_limit_to__all__False_ok(self):
633 def test_limit_to__all__False_ok(self):
634 """
634 """
635 Limit to all is deprecated, once we remove it this test can go away.
635 Limit to all is deprecated, once we remove it this test can go away.
636 """
636 """
637 ip = get_ipython()
637 ip = get_ipython()
638 c = ip.Completer
638 c = ip.Completer
639 c.use_jedi = False
639 c.use_jedi = False
640 ip.ex("class D: x=24")
640 ip.ex("class D: x=24")
641 ip.ex("d=D()")
641 ip.ex("d=D()")
642 cfg = Config()
642 cfg = Config()
643 cfg.IPCompleter.limit_to__all__ = False
643 cfg.IPCompleter.limit_to__all__ = False
644 c.update_config(cfg)
644 c.update_config(cfg)
645 s, matches = c.complete("d.")
645 s, matches = c.complete("d.")
646 self.assertIn("d.x", matches)
646 self.assertIn("d.x", matches)
647
647
648 def test_get__all__entries_ok(self):
648 def test_get__all__entries_ok(self):
649 class A:
649 class A:
650 __all__ = ["x", 1]
650 __all__ = ["x", 1]
651
651
652 words = completer.get__all__entries(A())
652 words = completer.get__all__entries(A())
653 self.assertEqual(words, ["x"])
653 self.assertEqual(words, ["x"])
654
654
655 def test_get__all__entries_no__all__ok(self):
655 def test_get__all__entries_no__all__ok(self):
656 class A:
656 class A:
657 pass
657 pass
658
658
659 words = completer.get__all__entries(A())
659 words = completer.get__all__entries(A())
660 self.assertEqual(words, [])
660 self.assertEqual(words, [])
661
661
662 def test_func_kw_completions(self):
662 def test_func_kw_completions(self):
663 ip = get_ipython()
663 ip = get_ipython()
664 c = ip.Completer
664 c = ip.Completer
665 c.use_jedi = False
665 c.use_jedi = False
666 ip.ex("def myfunc(a=1,b=2): return a+b")
666 ip.ex("def myfunc(a=1,b=2): return a+b")
667 s, matches = c.complete(None, "myfunc(1,b")
667 s, matches = c.complete(None, "myfunc(1,b")
668 self.assertIn("b=", matches)
668 self.assertIn("b=", matches)
669 # Simulate completing with cursor right after b (pos==10):
669 # Simulate completing with cursor right after b (pos==10):
670 s, matches = c.complete(None, "myfunc(1,b)", 10)
670 s, matches = c.complete(None, "myfunc(1,b)", 10)
671 self.assertIn("b=", matches)
671 self.assertIn("b=", matches)
672 s, matches = c.complete(None, 'myfunc(a="escaped\\")string",b')
672 s, matches = c.complete(None, 'myfunc(a="escaped\\")string",b')
673 self.assertIn("b=", matches)
673 self.assertIn("b=", matches)
674 # builtin function
674 # builtin function
675 s, matches = c.complete(None, "min(k, k")
675 s, matches = c.complete(None, "min(k, k")
676 self.assertIn("key=", matches)
676 self.assertIn("key=", matches)
677
677
678 def test_default_arguments_from_docstring(self):
678 def test_default_arguments_from_docstring(self):
679 ip = get_ipython()
679 ip = get_ipython()
680 c = ip.Completer
680 c = ip.Completer
681 kwd = c._default_arguments_from_docstring("min(iterable[, key=func]) -> value")
681 kwd = c._default_arguments_from_docstring("min(iterable[, key=func]) -> value")
682 self.assertEqual(kwd, ["key"])
682 self.assertEqual(kwd, ["key"])
683 # with cython type etc
683 # with cython type etc
684 kwd = c._default_arguments_from_docstring(
684 kwd = c._default_arguments_from_docstring(
685 "Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n"
685 "Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n"
686 )
686 )
687 self.assertEqual(kwd, ["ncall", "resume", "nsplit"])
687 self.assertEqual(kwd, ["ncall", "resume", "nsplit"])
688 # white spaces
688 # white spaces
689 kwd = c._default_arguments_from_docstring(
689 kwd = c._default_arguments_from_docstring(
690 "\n Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n"
690 "\n Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n"
691 )
691 )
692 self.assertEqual(kwd, ["ncall", "resume", "nsplit"])
692 self.assertEqual(kwd, ["ncall", "resume", "nsplit"])
693
693
694 def test_line_magics(self):
694 def test_line_magics(self):
695 ip = get_ipython()
695 ip = get_ipython()
696 c = ip.Completer
696 c = ip.Completer
697 s, matches = c.complete(None, "lsmag")
697 s, matches = c.complete(None, "lsmag")
698 self.assertIn("%lsmagic", matches)
698 self.assertIn("%lsmagic", matches)
699 s, matches = c.complete(None, "%lsmag")
699 s, matches = c.complete(None, "%lsmag")
700 self.assertIn("%lsmagic", matches)
700 self.assertIn("%lsmagic", matches)
701
701
702 def test_cell_magics(self):
702 def test_cell_magics(self):
703 from IPython.core.magic import register_cell_magic
703 from IPython.core.magic import register_cell_magic
704
704
705 @register_cell_magic
705 @register_cell_magic
706 def _foo_cellm(line, cell):
706 def _foo_cellm(line, cell):
707 pass
707 pass
708
708
709 ip = get_ipython()
709 ip = get_ipython()
710 c = ip.Completer
710 c = ip.Completer
711
711
712 s, matches = c.complete(None, "_foo_ce")
712 s, matches = c.complete(None, "_foo_ce")
713 self.assertIn("%%_foo_cellm", matches)
713 self.assertIn("%%_foo_cellm", matches)
714 s, matches = c.complete(None, "%%_foo_ce")
714 s, matches = c.complete(None, "%%_foo_ce")
715 self.assertIn("%%_foo_cellm", matches)
715 self.assertIn("%%_foo_cellm", matches)
716
716
717 def test_line_cell_magics(self):
717 def test_line_cell_magics(self):
718 from IPython.core.magic import register_line_cell_magic
718 from IPython.core.magic import register_line_cell_magic
719
719
720 @register_line_cell_magic
720 @register_line_cell_magic
721 def _bar_cellm(line, cell):
721 def _bar_cellm(line, cell):
722 pass
722 pass
723
723
724 ip = get_ipython()
724 ip = get_ipython()
725 c = ip.Completer
725 c = ip.Completer
726
726
727 # The policy here is trickier, see comments in completion code. The
727 # The policy here is trickier, see comments in completion code. The
728 # returned values depend on whether the user passes %% or not explicitly,
728 # returned values depend on whether the user passes %% or not explicitly,
729 # and this will show a difference if the same name is both a line and cell
729 # and this will show a difference if the same name is both a line and cell
730 # magic.
730 # magic.
731 s, matches = c.complete(None, "_bar_ce")
731 s, matches = c.complete(None, "_bar_ce")
732 self.assertIn("%_bar_cellm", matches)
732 self.assertIn("%_bar_cellm", matches)
733 self.assertIn("%%_bar_cellm", matches)
733 self.assertIn("%%_bar_cellm", matches)
734 s, matches = c.complete(None, "%_bar_ce")
734 s, matches = c.complete(None, "%_bar_ce")
735 self.assertIn("%_bar_cellm", matches)
735 self.assertIn("%_bar_cellm", matches)
736 self.assertIn("%%_bar_cellm", matches)
736 self.assertIn("%%_bar_cellm", matches)
737 s, matches = c.complete(None, "%%_bar_ce")
737 s, matches = c.complete(None, "%%_bar_ce")
738 self.assertNotIn("%_bar_cellm", matches)
738 self.assertNotIn("%_bar_cellm", matches)
739 self.assertIn("%%_bar_cellm", matches)
739 self.assertIn("%%_bar_cellm", matches)
740
740
741 def test_magic_completion_order(self):
741 def test_magic_completion_order(self):
742 ip = get_ipython()
742 ip = get_ipython()
743 c = ip.Completer
743 c = ip.Completer
744
744
745 # Test ordering of line and cell magics.
745 # Test ordering of line and cell magics.
746 text, matches = c.complete("timeit")
746 text, matches = c.complete("timeit")
747 self.assertEqual(matches, ["%timeit", "%%timeit"])
747 self.assertEqual(matches, ["%timeit", "%%timeit"])
748
748
749 def test_magic_completion_shadowing(self):
749 def test_magic_completion_shadowing(self):
750 ip = get_ipython()
750 ip = get_ipython()
751 c = ip.Completer
751 c = ip.Completer
752 c.use_jedi = False
752 c.use_jedi = False
753
753
754 # Before importing matplotlib, %matplotlib magic should be the only option.
754 # Before importing matplotlib, %matplotlib magic should be the only option.
755 text, matches = c.complete("mat")
755 text, matches = c.complete("mat")
756 self.assertEqual(matches, ["%matplotlib"])
756 self.assertEqual(matches, ["%matplotlib"])
757
757
758 # The newly introduced name should shadow the magic.
758 # The newly introduced name should shadow the magic.
759 ip.run_cell("matplotlib = 1")
759 ip.run_cell("matplotlib = 1")
760 text, matches = c.complete("mat")
760 text, matches = c.complete("mat")
761 self.assertEqual(matches, ["matplotlib"])
761 self.assertEqual(matches, ["matplotlib"])
762
762
763 # After removing matplotlib from namespace, the magic should again be
763 # After removing matplotlib from namespace, the magic should again be
764 # the only option.
764 # the only option.
765 del ip.user_ns["matplotlib"]
765 del ip.user_ns["matplotlib"]
766 text, matches = c.complete("mat")
766 text, matches = c.complete("mat")
767 self.assertEqual(matches, ["%matplotlib"])
767 self.assertEqual(matches, ["%matplotlib"])
768
768
769 def test_magic_completion_shadowing_explicit(self):
769 def test_magic_completion_shadowing_explicit(self):
770 """
770 """
771 If the user try to complete a shadowed magic, and explicit % start should
771 If the user try to complete a shadowed magic, and explicit % start should
772 still return the completions.
772 still return the completions.
773 """
773 """
774 ip = get_ipython()
774 ip = get_ipython()
775 c = ip.Completer
775 c = ip.Completer
776
776
777 # Before importing matplotlib, %matplotlib magic should be the only option.
777 # Before importing matplotlib, %matplotlib magic should be the only option.
778 text, matches = c.complete("%mat")
778 text, matches = c.complete("%mat")
779 self.assertEqual(matches, ["%matplotlib"])
779 self.assertEqual(matches, ["%matplotlib"])
780
780
781 ip.run_cell("matplotlib = 1")
781 ip.run_cell("matplotlib = 1")
782
782
783 # After removing matplotlib from namespace, the magic should still be
783 # After removing matplotlib from namespace, the magic should still be
784 # the only option.
784 # the only option.
785 text, matches = c.complete("%mat")
785 text, matches = c.complete("%mat")
786 self.assertEqual(matches, ["%matplotlib"])
786 self.assertEqual(matches, ["%matplotlib"])
787
787
788 def test_magic_config(self):
788 def test_magic_config(self):
789 ip = get_ipython()
789 ip = get_ipython()
790 c = ip.Completer
790 c = ip.Completer
791
791
792 s, matches = c.complete(None, "conf")
792 s, matches = c.complete(None, "conf")
793 self.assertIn("%config", matches)
793 self.assertIn("%config", matches)
794 s, matches = c.complete(None, "conf")
794 s, matches = c.complete(None, "conf")
795 self.assertNotIn("AliasManager", matches)
795 self.assertNotIn("AliasManager", matches)
796 s, matches = c.complete(None, "config ")
796 s, matches = c.complete(None, "config ")
797 self.assertIn("AliasManager", matches)
797 self.assertIn("AliasManager", matches)
798 s, matches = c.complete(None, "%config ")
798 s, matches = c.complete(None, "%config ")
799 self.assertIn("AliasManager", matches)
799 self.assertIn("AliasManager", matches)
800 s, matches = c.complete(None, "config Ali")
800 s, matches = c.complete(None, "config Ali")
801 self.assertListEqual(["AliasManager"], matches)
801 self.assertListEqual(["AliasManager"], matches)
802 s, matches = c.complete(None, "%config Ali")
802 s, matches = c.complete(None, "%config Ali")
803 self.assertListEqual(["AliasManager"], matches)
803 self.assertListEqual(["AliasManager"], matches)
804 s, matches = c.complete(None, "config AliasManager")
804 s, matches = c.complete(None, "config AliasManager")
805 self.assertListEqual(["AliasManager"], matches)
805 self.assertListEqual(["AliasManager"], matches)
806 s, matches = c.complete(None, "%config AliasManager")
806 s, matches = c.complete(None, "%config AliasManager")
807 self.assertListEqual(["AliasManager"], matches)
807 self.assertListEqual(["AliasManager"], matches)
808 s, matches = c.complete(None, "config AliasManager.")
808 s, matches = c.complete(None, "config AliasManager.")
809 self.assertIn("AliasManager.default_aliases", matches)
809 self.assertIn("AliasManager.default_aliases", matches)
810 s, matches = c.complete(None, "%config AliasManager.")
810 s, matches = c.complete(None, "%config AliasManager.")
811 self.assertIn("AliasManager.default_aliases", matches)
811 self.assertIn("AliasManager.default_aliases", matches)
812 s, matches = c.complete(None, "config AliasManager.de")
812 s, matches = c.complete(None, "config AliasManager.de")
813 self.assertListEqual(["AliasManager.default_aliases"], matches)
813 self.assertListEqual(["AliasManager.default_aliases"], matches)
814 s, matches = c.complete(None, "config AliasManager.de")
814 s, matches = c.complete(None, "config AliasManager.de")
815 self.assertListEqual(["AliasManager.default_aliases"], matches)
815 self.assertListEqual(["AliasManager.default_aliases"], matches)
816
816
817 def test_magic_color(self):
817 def test_magic_color(self):
818 ip = get_ipython()
818 ip = get_ipython()
819 c = ip.Completer
819 c = ip.Completer
820
820
821 s, matches = c.complete(None, "colo")
821 s, matches = c.complete(None, "colo")
822 self.assertIn("%colors", matches)
822 self.assertIn("%colors", matches)
823 s, matches = c.complete(None, "colo")
823 s, matches = c.complete(None, "colo")
824 self.assertNotIn("NoColor", matches)
824 self.assertNotIn("NoColor", matches)
825 s, matches = c.complete(None, "%colors") # No trailing space
825 s, matches = c.complete(None, "%colors") # No trailing space
826 self.assertNotIn("NoColor", matches)
826 self.assertNotIn("NoColor", matches)
827 s, matches = c.complete(None, "colors ")
827 s, matches = c.complete(None, "colors ")
828 self.assertIn("NoColor", matches)
828 self.assertIn("NoColor", matches)
829 s, matches = c.complete(None, "%colors ")
829 s, matches = c.complete(None, "%colors ")
830 self.assertIn("NoColor", matches)
830 self.assertIn("NoColor", matches)
831 s, matches = c.complete(None, "colors NoCo")
831 s, matches = c.complete(None, "colors NoCo")
832 self.assertListEqual(["NoColor"], matches)
832 self.assertListEqual(["NoColor"], matches)
833 s, matches = c.complete(None, "%colors NoCo")
833 s, matches = c.complete(None, "%colors NoCo")
834 self.assertListEqual(["NoColor"], matches)
834 self.assertListEqual(["NoColor"], matches)
835
835
836 def test_match_dict_keys(self):
836 def test_match_dict_keys(self):
837 """
837 """
838 Test that match_dict_keys works on a couple of use case does return what
838 Test that match_dict_keys works on a couple of use case does return what
839 expected, and does not crash
839 expected, and does not crash
840 """
840 """
841 delims = " \t\n`!@#$^&*()=+[{]}\\|;:'\",<>?"
841 delims = " \t\n`!@#$^&*()=+[{]}\\|;:'\",<>?"
842
842
843 keys = ["foo", b"far"]
843 keys = ["foo", b"far"]
844 assert match_dict_keys(keys, "b'", delims=delims) == ("'", 2, ["far"])
844 assert match_dict_keys(keys, "b'", delims=delims) == ("'", 2, ["far"])
845 assert match_dict_keys(keys, "b'f", delims=delims) == ("'", 2, ["far"])
845 assert match_dict_keys(keys, "b'f", delims=delims) == ("'", 2, ["far"])
846 assert match_dict_keys(keys, 'b"', delims=delims) == ('"', 2, ["far"])
846 assert match_dict_keys(keys, 'b"', delims=delims) == ('"', 2, ["far"])
847 assert match_dict_keys(keys, 'b"f', delims=delims) == ('"', 2, ["far"])
847 assert match_dict_keys(keys, 'b"f', delims=delims) == ('"', 2, ["far"])
848
848
849 assert match_dict_keys(keys, "'", delims=delims) == ("'", 1, ["foo"])
849 assert match_dict_keys(keys, "'", delims=delims) == ("'", 1, ["foo"])
850 assert match_dict_keys(keys, "'f", delims=delims) == ("'", 1, ["foo"])
850 assert match_dict_keys(keys, "'f", delims=delims) == ("'", 1, ["foo"])
851 assert match_dict_keys(keys, '"', delims=delims) == ('"', 1, ["foo"])
851 assert match_dict_keys(keys, '"', delims=delims) == ('"', 1, ["foo"])
852 assert match_dict_keys(keys, '"f', delims=delims) == ('"', 1, ["foo"])
852 assert match_dict_keys(keys, '"f', delims=delims) == ('"', 1, ["foo"])
853
853
854 match_dict_keys
854 match_dict_keys
855
855
856 def test_match_dict_keys_tuple(self):
856 def test_match_dict_keys_tuple(self):
857 """
857 """
858 Test that match_dict_keys called with extra prefix works on a couple of use case,
858 Test that match_dict_keys called with extra prefix works on a couple of use case,
859 does return what expected, and does not crash.
859 does return what expected, and does not crash.
860 """
860 """
861 delims = " \t\n`!@#$^&*()=+[{]}\\|;:'\",<>?"
861 delims = " \t\n`!@#$^&*()=+[{]}\\|;:'\",<>?"
862
862
863 keys = [("foo", "bar"), ("foo", "oof"), ("foo", b"bar"), ('other', 'test')]
863 keys = [("foo", "bar"), ("foo", "oof"), ("foo", b"bar"), ('other', 'test')]
864
864
865 # Completion on first key == "foo"
865 # Completion on first key == "foo"
866 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("foo",)) == ("'", 1, ["bar", "oof"])
866 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("foo",)) == ("'", 1, ["bar", "oof"])
867 assert match_dict_keys(keys, "\"", delims=delims, extra_prefix=("foo",)) == ("\"", 1, ["bar", "oof"])
867 assert match_dict_keys(keys, "\"", delims=delims, extra_prefix=("foo",)) == ("\"", 1, ["bar", "oof"])
868 assert match_dict_keys(keys, "'o", delims=delims, extra_prefix=("foo",)) == ("'", 1, ["oof"])
868 assert match_dict_keys(keys, "'o", delims=delims, extra_prefix=("foo",)) == ("'", 1, ["oof"])
869 assert match_dict_keys(keys, "\"o", delims=delims, extra_prefix=("foo",)) == ("\"", 1, ["oof"])
869 assert match_dict_keys(keys, "\"o", delims=delims, extra_prefix=("foo",)) == ("\"", 1, ["oof"])
870 assert match_dict_keys(keys, "b'", delims=delims, extra_prefix=("foo",)) == ("'", 2, ["bar"])
870 assert match_dict_keys(keys, "b'", delims=delims, extra_prefix=("foo",)) == ("'", 2, ["bar"])
871 assert match_dict_keys(keys, "b\"", delims=delims, extra_prefix=("foo",)) == ("\"", 2, ["bar"])
871 assert match_dict_keys(keys, "b\"", delims=delims, extra_prefix=("foo",)) == ("\"", 2, ["bar"])
872 assert match_dict_keys(keys, "b'b", delims=delims, extra_prefix=("foo",)) == ("'", 2, ["bar"])
872 assert match_dict_keys(keys, "b'b", delims=delims, extra_prefix=("foo",)) == ("'", 2, ["bar"])
873 assert match_dict_keys(keys, "b\"b", delims=delims, extra_prefix=("foo",)) == ("\"", 2, ["bar"])
873 assert match_dict_keys(keys, "b\"b", delims=delims, extra_prefix=("foo",)) == ("\"", 2, ["bar"])
874
874
875 # No Completion
875 # No Completion
876 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("no_foo",)) == ("'", 1, [])
876 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("no_foo",)) == ("'", 1, [])
877 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("fo",)) == ("'", 1, [])
877 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("fo",)) == ("'", 1, [])
878
878
879 keys = [('foo1', 'foo2', 'foo3', 'foo4'), ('foo1', 'foo2', 'bar', 'foo4')]
879 keys = [('foo1', 'foo2', 'foo3', 'foo4'), ('foo1', 'foo2', 'bar', 'foo4')]
880 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1',)) == ("'", 1, ["foo2", "foo2"])
880 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1',)) == ("'", 1, ["foo2", "foo2"])
881 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2')) == ("'", 1, ["foo3"])
881 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2')) == ("'", 1, ["foo3"])
882 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2', 'foo3')) == ("'", 1, ["foo4"])
882 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2', 'foo3')) == ("'", 1, ["foo4"])
883 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2', 'foo3', 'foo4')) == ("'", 1, [])
883 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2', 'foo3', 'foo4')) == ("'", 1, [])
884
884
885 def test_dict_key_completion_string(self):
885 def test_dict_key_completion_string(self):
886 """Test dictionary key completion for string keys"""
886 """Test dictionary key completion for string keys"""
887 ip = get_ipython()
887 ip = get_ipython()
888 complete = ip.Completer.complete
888 complete = ip.Completer.complete
889
889
890 ip.user_ns["d"] = {"abc": None}
890 ip.user_ns["d"] = {"abc": None}
891
891
892 # check completion at different stages
892 # check completion at different stages
893 _, matches = complete(line_buffer="d[")
893 _, matches = complete(line_buffer="d[")
894 self.assertIn("'abc'", matches)
894 self.assertIn("'abc'", matches)
895 self.assertNotIn("'abc']", matches)
895 self.assertNotIn("'abc']", matches)
896
896
897 _, matches = complete(line_buffer="d['")
897 _, matches = complete(line_buffer="d['")
898 self.assertIn("abc", matches)
898 self.assertIn("abc", matches)
899 self.assertNotIn("abc']", matches)
899 self.assertNotIn("abc']", matches)
900
900
901 _, matches = complete(line_buffer="d['a")
901 _, matches = complete(line_buffer="d['a")
902 self.assertIn("abc", matches)
902 self.assertIn("abc", matches)
903 self.assertNotIn("abc']", matches)
903 self.assertNotIn("abc']", matches)
904
904
905 # check use of different quoting
905 # check use of different quoting
906 _, matches = complete(line_buffer='d["')
906 _, matches = complete(line_buffer='d["')
907 self.assertIn("abc", matches)
907 self.assertIn("abc", matches)
908 self.assertNotIn('abc"]', matches)
908 self.assertNotIn('abc"]', matches)
909
909
910 _, matches = complete(line_buffer='d["a')
910 _, matches = complete(line_buffer='d["a')
911 self.assertIn("abc", matches)
911 self.assertIn("abc", matches)
912 self.assertNotIn('abc"]', matches)
912 self.assertNotIn('abc"]', matches)
913
913
914 # check sensitivity to following context
914 # check sensitivity to following context
915 _, matches = complete(line_buffer="d[]", cursor_pos=2)
915 _, matches = complete(line_buffer="d[]", cursor_pos=2)
916 self.assertIn("'abc'", matches)
916 self.assertIn("'abc'", matches)
917
917
918 _, matches = complete(line_buffer="d['']", cursor_pos=3)
918 _, matches = complete(line_buffer="d['']", cursor_pos=3)
919 self.assertIn("abc", matches)
919 self.assertIn("abc", matches)
920 self.assertNotIn("abc'", matches)
920 self.assertNotIn("abc'", matches)
921 self.assertNotIn("abc']", matches)
921 self.assertNotIn("abc']", matches)
922
922
923 # check multiple solutions are correctly returned and that noise is not
923 # check multiple solutions are correctly returned and that noise is not
924 ip.user_ns["d"] = {
924 ip.user_ns["d"] = {
925 "abc": None,
925 "abc": None,
926 "abd": None,
926 "abd": None,
927 "bad": None,
927 "bad": None,
928 object(): None,
928 object(): None,
929 5: None,
929 5: None,
930 ("abe", None): None,
930 ("abe", None): None,
931 (None, "abf"): None
931 (None, "abf"): None
932 }
932 }
933
933
934 _, matches = complete(line_buffer="d['a")
934 _, matches = complete(line_buffer="d['a")
935 self.assertIn("abc", matches)
935 self.assertIn("abc", matches)
936 self.assertIn("abd", matches)
936 self.assertIn("abd", matches)
937 self.assertNotIn("bad", matches)
937 self.assertNotIn("bad", matches)
938 self.assertNotIn("abe", matches)
938 self.assertNotIn("abe", matches)
939 self.assertNotIn("abf", matches)
939 self.assertNotIn("abf", matches)
940 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
940 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
941
941
942 # check escaping and whitespace
942 # check escaping and whitespace
943 ip.user_ns["d"] = {"a\nb": None, "a'b": None, 'a"b': None, "a word": None}
943 ip.user_ns["d"] = {"a\nb": None, "a'b": None, 'a"b': None, "a word": None}
944 _, matches = complete(line_buffer="d['a")
944 _, matches = complete(line_buffer="d['a")
945 self.assertIn("a\\nb", matches)
945 self.assertIn("a\\nb", matches)
946 self.assertIn("a\\'b", matches)
946 self.assertIn("a\\'b", matches)
947 self.assertIn('a"b', matches)
947 self.assertIn('a"b', matches)
948 self.assertIn("a word", matches)
948 self.assertIn("a word", matches)
949 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
949 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
950
950
951 # - can complete on non-initial word of the string
951 # - can complete on non-initial word of the string
952 _, matches = complete(line_buffer="d['a w")
952 _, matches = complete(line_buffer="d['a w")
953 self.assertIn("word", matches)
953 self.assertIn("word", matches)
954
954
955 # - understands quote escaping
955 # - understands quote escaping
956 _, matches = complete(line_buffer="d['a\\'")
956 _, matches = complete(line_buffer="d['a\\'")
957 self.assertIn("b", matches)
957 self.assertIn("b", matches)
958
958
959 # - default quoting should work like repr
959 # - default quoting should work like repr
960 _, matches = complete(line_buffer="d[")
960 _, matches = complete(line_buffer="d[")
961 self.assertIn('"a\'b"', matches)
961 self.assertIn('"a\'b"', matches)
962
962
963 # - when opening quote with ", possible to match with unescaped apostrophe
963 # - when opening quote with ", possible to match with unescaped apostrophe
964 _, matches = complete(line_buffer="d[\"a'")
964 _, matches = complete(line_buffer="d[\"a'")
965 self.assertIn("b", matches)
965 self.assertIn("b", matches)
966
966
967 # need to not split at delims that readline won't split at
967 # need to not split at delims that readline won't split at
968 if "-" not in ip.Completer.splitter.delims:
968 if "-" not in ip.Completer.splitter.delims:
969 ip.user_ns["d"] = {"before-after": None}
969 ip.user_ns["d"] = {"before-after": None}
970 _, matches = complete(line_buffer="d['before-af")
970 _, matches = complete(line_buffer="d['before-af")
971 self.assertIn("before-after", matches)
971 self.assertIn("before-after", matches)
972
972
973 # check completion on tuple-of-string keys at different stage - on first key
973 # check completion on tuple-of-string keys at different stage - on first key
974 ip.user_ns["d"] = {('foo', 'bar'): None}
974 ip.user_ns["d"] = {('foo', 'bar'): None}
975 _, matches = complete(line_buffer="d[")
975 _, matches = complete(line_buffer="d[")
976 self.assertIn("'foo'", matches)
976 self.assertIn("'foo'", matches)
977 self.assertNotIn("'foo']", matches)
977 self.assertNotIn("'foo']", matches)
978 self.assertNotIn("'bar'", matches)
978 self.assertNotIn("'bar'", matches)
979 self.assertNotIn("foo", matches)
979 self.assertNotIn("foo", matches)
980 self.assertNotIn("bar", matches)
980 self.assertNotIn("bar", matches)
981
981
982 # - match the prefix
982 # - match the prefix
983 _, matches = complete(line_buffer="d['f")
983 _, matches = complete(line_buffer="d['f")
984 self.assertIn("foo", matches)
984 self.assertIn("foo", matches)
985 self.assertNotIn("foo']", matches)
985 self.assertNotIn("foo']", matches)
986 self.assertNotIn('foo"]', matches)
986 self.assertNotIn('foo"]', matches)
987 _, matches = complete(line_buffer="d['foo")
987 _, matches = complete(line_buffer="d['foo")
988 self.assertIn("foo", matches)
988 self.assertIn("foo", matches)
989
989
990 # - can complete on second key
990 # - can complete on second key
991 _, matches = complete(line_buffer="d['foo', ")
991 _, matches = complete(line_buffer="d['foo', ")
992 self.assertIn("'bar'", matches)
992 self.assertIn("'bar'", matches)
993 _, matches = complete(line_buffer="d['foo', 'b")
993 _, matches = complete(line_buffer="d['foo', 'b")
994 self.assertIn("bar", matches)
994 self.assertIn("bar", matches)
995 self.assertNotIn("foo", matches)
995 self.assertNotIn("foo", matches)
996
996
997 # - does not propose missing keys
997 # - does not propose missing keys
998 _, matches = complete(line_buffer="d['foo', 'f")
998 _, matches = complete(line_buffer="d['foo', 'f")
999 self.assertNotIn("bar", matches)
999 self.assertNotIn("bar", matches)
1000 self.assertNotIn("foo", matches)
1000 self.assertNotIn("foo", matches)
1001
1001
1002 # check sensitivity to following context
1002 # check sensitivity to following context
1003 _, matches = complete(line_buffer="d['foo',]", cursor_pos=8)
1003 _, matches = complete(line_buffer="d['foo',]", cursor_pos=8)
1004 self.assertIn("'bar'", matches)
1004 self.assertIn("'bar'", matches)
1005 self.assertNotIn("bar", matches)
1005 self.assertNotIn("bar", matches)
1006 self.assertNotIn("'foo'", matches)
1006 self.assertNotIn("'foo'", matches)
1007 self.assertNotIn("foo", matches)
1007 self.assertNotIn("foo", matches)
1008
1008
1009 _, matches = complete(line_buffer="d['']", cursor_pos=3)
1009 _, matches = complete(line_buffer="d['']", cursor_pos=3)
1010 self.assertIn("foo", matches)
1010 self.assertIn("foo", matches)
1011 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
1011 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
1012
1012
1013 _, matches = complete(line_buffer='d[""]', cursor_pos=3)
1013 _, matches = complete(line_buffer='d[""]', cursor_pos=3)
1014 self.assertIn("foo", matches)
1014 self.assertIn("foo", matches)
1015 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
1015 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
1016
1016
1017 _, matches = complete(line_buffer='d["foo","]', cursor_pos=9)
1017 _, matches = complete(line_buffer='d["foo","]', cursor_pos=9)
1018 self.assertIn("bar", matches)
1018 self.assertIn("bar", matches)
1019 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
1019 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
1020
1020
1021 _, matches = complete(line_buffer='d["foo",]', cursor_pos=8)
1021 _, matches = complete(line_buffer='d["foo",]', cursor_pos=8)
1022 self.assertIn("'bar'", matches)
1022 self.assertIn("'bar'", matches)
1023 self.assertNotIn("bar", matches)
1023 self.assertNotIn("bar", matches)
1024
1024
1025 # Can complete with longer tuple keys
1025 # Can complete with longer tuple keys
1026 ip.user_ns["d"] = {('foo', 'bar', 'foobar'): None}
1026 ip.user_ns["d"] = {('foo', 'bar', 'foobar'): None}
1027
1027
1028 # - can complete second key
1028 # - can complete second key
1029 _, matches = complete(line_buffer="d['foo', 'b")
1029 _, matches = complete(line_buffer="d['foo', 'b")
1030 self.assertIn("bar", matches)
1030 self.assertIn("bar", matches)
1031 self.assertNotIn("foo", matches)
1031 self.assertNotIn("foo", matches)
1032 self.assertNotIn("foobar", matches)
1032 self.assertNotIn("foobar", matches)
1033
1033
1034 # - can complete third key
1034 # - can complete third key
1035 _, matches = complete(line_buffer="d['foo', 'bar', 'fo")
1035 _, matches = complete(line_buffer="d['foo', 'bar', 'fo")
1036 self.assertIn("foobar", matches)
1036 self.assertIn("foobar", matches)
1037 self.assertNotIn("foo", matches)
1037 self.assertNotIn("foo", matches)
1038 self.assertNotIn("bar", matches)
1038 self.assertNotIn("bar", matches)
1039
1039
1040 def test_dict_key_completion_contexts(self):
1040 def test_dict_key_completion_contexts(self):
1041 """Test expression contexts in which dict key completion occurs"""
1041 """Test expression contexts in which dict key completion occurs"""
1042 ip = get_ipython()
1042 ip = get_ipython()
1043 complete = ip.Completer.complete
1043 complete = ip.Completer.complete
1044 d = {"abc": None}
1044 d = {"abc": None}
1045 ip.user_ns["d"] = d
1045 ip.user_ns["d"] = d
1046
1046
1047 class C:
1047 class C:
1048 data = d
1048 data = d
1049
1049
1050 ip.user_ns["C"] = C
1050 ip.user_ns["C"] = C
1051 ip.user_ns["get"] = lambda: d
1051 ip.user_ns["get"] = lambda: d
1052
1052
1053 def assert_no_completion(**kwargs):
1053 def assert_no_completion(**kwargs):
1054 _, matches = complete(**kwargs)
1054 _, matches = complete(**kwargs)
1055 self.assertNotIn("abc", matches)
1055 self.assertNotIn("abc", matches)
1056 self.assertNotIn("abc'", matches)
1056 self.assertNotIn("abc'", matches)
1057 self.assertNotIn("abc']", matches)
1057 self.assertNotIn("abc']", matches)
1058 self.assertNotIn("'abc'", matches)
1058 self.assertNotIn("'abc'", matches)
1059 self.assertNotIn("'abc']", matches)
1059 self.assertNotIn("'abc']", matches)
1060
1060
1061 def assert_completion(**kwargs):
1061 def assert_completion(**kwargs):
1062 _, matches = complete(**kwargs)
1062 _, matches = complete(**kwargs)
1063 self.assertIn("'abc'", matches)
1063 self.assertIn("'abc'", matches)
1064 self.assertNotIn("'abc']", matches)
1064 self.assertNotIn("'abc']", matches)
1065
1065
1066 # no completion after string closed, even if reopened
1066 # no completion after string closed, even if reopened
1067 assert_no_completion(line_buffer="d['a'")
1067 assert_no_completion(line_buffer="d['a'")
1068 assert_no_completion(line_buffer='d["a"')
1068 assert_no_completion(line_buffer='d["a"')
1069 assert_no_completion(line_buffer="d['a' + ")
1069 assert_no_completion(line_buffer="d['a' + ")
1070 assert_no_completion(line_buffer="d['a' + '")
1070 assert_no_completion(line_buffer="d['a' + '")
1071
1071
1072 # completion in non-trivial expressions
1072 # completion in non-trivial expressions
1073 assert_completion(line_buffer="+ d[")
1073 assert_completion(line_buffer="+ d[")
1074 assert_completion(line_buffer="(d[")
1074 assert_completion(line_buffer="(d[")
1075 assert_completion(line_buffer="C.data[")
1075 assert_completion(line_buffer="C.data[")
1076
1076
1077 # greedy flag
1077 # greedy flag
1078 def assert_completion(**kwargs):
1078 def assert_completion(**kwargs):
1079 _, matches = complete(**kwargs)
1079 _, matches = complete(**kwargs)
1080 self.assertIn("get()['abc']", matches)
1080 self.assertIn("get()['abc']", matches)
1081
1081
1082 assert_no_completion(line_buffer="get()[")
1082 assert_no_completion(line_buffer="get()[")
1083 with greedy_completion():
1083 with greedy_completion():
1084 assert_completion(line_buffer="get()[")
1084 assert_completion(line_buffer="get()[")
1085 assert_completion(line_buffer="get()['")
1085 assert_completion(line_buffer="get()['")
1086 assert_completion(line_buffer="get()['a")
1086 assert_completion(line_buffer="get()['a")
1087 assert_completion(line_buffer="get()['ab")
1087 assert_completion(line_buffer="get()['ab")
1088 assert_completion(line_buffer="get()['abc")
1088 assert_completion(line_buffer="get()['abc")
1089
1089
1090 def test_dict_key_completion_bytes(self):
1090 def test_dict_key_completion_bytes(self):
1091 """Test handling of bytes in dict key completion"""
1091 """Test handling of bytes in dict key completion"""
1092 ip = get_ipython()
1092 ip = get_ipython()
1093 complete = ip.Completer.complete
1093 complete = ip.Completer.complete
1094
1094
1095 ip.user_ns["d"] = {"abc": None, b"abd": None}
1095 ip.user_ns["d"] = {"abc": None, b"abd": None}
1096
1096
1097 _, matches = complete(line_buffer="d[")
1097 _, matches = complete(line_buffer="d[")
1098 self.assertIn("'abc'", matches)
1098 self.assertIn("'abc'", matches)
1099 self.assertIn("b'abd'", matches)
1099 self.assertIn("b'abd'", matches)
1100
1100
1101 if False: # not currently implemented
1101 if False: # not currently implemented
1102 _, matches = complete(line_buffer="d[b")
1102 _, matches = complete(line_buffer="d[b")
1103 self.assertIn("b'abd'", matches)
1103 self.assertIn("b'abd'", matches)
1104 self.assertNotIn("b'abc'", matches)
1104 self.assertNotIn("b'abc'", matches)
1105
1105
1106 _, matches = complete(line_buffer="d[b'")
1106 _, matches = complete(line_buffer="d[b'")
1107 self.assertIn("abd", matches)
1107 self.assertIn("abd", matches)
1108 self.assertNotIn("abc", matches)
1108 self.assertNotIn("abc", matches)
1109
1109
1110 _, matches = complete(line_buffer="d[B'")
1110 _, matches = complete(line_buffer="d[B'")
1111 self.assertIn("abd", matches)
1111 self.assertIn("abd", matches)
1112 self.assertNotIn("abc", matches)
1112 self.assertNotIn("abc", matches)
1113
1113
1114 _, matches = complete(line_buffer="d['")
1114 _, matches = complete(line_buffer="d['")
1115 self.assertIn("abc", matches)
1115 self.assertIn("abc", matches)
1116 self.assertNotIn("abd", matches)
1116 self.assertNotIn("abd", matches)
1117
1117
1118 def test_dict_key_completion_unicode_py3(self):
1118 def test_dict_key_completion_unicode_py3(self):
1119 """Test handling of unicode in dict key completion"""
1119 """Test handling of unicode in dict key completion"""
1120 ip = get_ipython()
1120 ip = get_ipython()
1121 complete = ip.Completer.complete
1121 complete = ip.Completer.complete
1122
1122
1123 ip.user_ns["d"] = {"a\u05d0": None}
1123 ip.user_ns["d"] = {"a\u05d0": None}
1124
1124
1125 # query using escape
1125 # query using escape
1126 if sys.platform != "win32":
1126 if sys.platform != "win32":
1127 # Known failure on Windows
1127 # Known failure on Windows
1128 _, matches = complete(line_buffer="d['a\\u05d0")
1128 _, matches = complete(line_buffer="d['a\\u05d0")
1129 self.assertIn("u05d0", matches) # tokenized after \\
1129 self.assertIn("u05d0", matches) # tokenized after \\
1130
1130
1131 # query using character
1131 # query using character
1132 _, matches = complete(line_buffer="d['a\u05d0")
1132 _, matches = complete(line_buffer="d['a\u05d0")
1133 self.assertIn("a\u05d0", matches)
1133 self.assertIn("a\u05d0", matches)
1134
1134
1135 with greedy_completion():
1135 with greedy_completion():
1136 # query using escape
1136 # query using escape
1137 _, matches = complete(line_buffer="d['a\\u05d0")
1137 _, matches = complete(line_buffer="d['a\\u05d0")
1138 self.assertIn("d['a\\u05d0']", matches) # tokenized after \\
1138 self.assertIn("d['a\\u05d0']", matches) # tokenized after \\
1139
1139
1140 # query using character
1140 # query using character
1141 _, matches = complete(line_buffer="d['a\u05d0")
1141 _, matches = complete(line_buffer="d['a\u05d0")
1142 self.assertIn("d['a\u05d0']", matches)
1142 self.assertIn("d['a\u05d0']", matches)
1143
1143
1144 @dec.skip_without("numpy")
1144 @dec.skip_without("numpy")
1145 def test_struct_array_key_completion(self):
1145 def test_struct_array_key_completion(self):
1146 """Test dict key completion applies to numpy struct arrays"""
1146 """Test dict key completion applies to numpy struct arrays"""
1147 import numpy
1147 import numpy
1148
1148
1149 ip = get_ipython()
1149 ip = get_ipython()
1150 complete = ip.Completer.complete
1150 complete = ip.Completer.complete
1151 ip.user_ns["d"] = numpy.array([], dtype=[("hello", "f"), ("world", "f")])
1151 ip.user_ns["d"] = numpy.array([], dtype=[("hello", "f"), ("world", "f")])
1152 _, matches = complete(line_buffer="d['")
1152 _, matches = complete(line_buffer="d['")
1153 self.assertIn("hello", matches)
1153 self.assertIn("hello", matches)
1154 self.assertIn("world", matches)
1154 self.assertIn("world", matches)
1155 # complete on the numpy struct itself
1155 # complete on the numpy struct itself
1156 dt = numpy.dtype(
1156 dt = numpy.dtype(
1157 [("my_head", [("my_dt", ">u4"), ("my_df", ">u4")]), ("my_data", ">f4", 5)]
1157 [("my_head", [("my_dt", ">u4"), ("my_df", ">u4")]), ("my_data", ">f4", 5)]
1158 )
1158 )
1159 x = numpy.zeros(2, dtype=dt)
1159 x = numpy.zeros(2, dtype=dt)
1160 ip.user_ns["d"] = x[1]
1160 ip.user_ns["d"] = x[1]
1161 _, matches = complete(line_buffer="d['")
1161 _, matches = complete(line_buffer="d['")
1162 self.assertIn("my_head", matches)
1162 self.assertIn("my_head", matches)
1163 self.assertIn("my_data", matches)
1163 self.assertIn("my_data", matches)
1164 # complete on a nested level
1164 # complete on a nested level
1165 with greedy_completion():
1165 with greedy_completion():
1166 ip.user_ns["d"] = numpy.zeros(2, dtype=dt)
1166 ip.user_ns["d"] = numpy.zeros(2, dtype=dt)
1167 _, matches = complete(line_buffer="d[1]['my_head']['")
1167 _, matches = complete(line_buffer="d[1]['my_head']['")
1168 self.assertTrue(any(["my_dt" in m for m in matches]))
1168 self.assertTrue(any(["my_dt" in m for m in matches]))
1169 self.assertTrue(any(["my_df" in m for m in matches]))
1169 self.assertTrue(any(["my_df" in m for m in matches]))
1170
1170
1171 @dec.skip_without("pandas")
1171 @dec.skip_without("pandas")
1172 def test_dataframe_key_completion(self):
1172 def test_dataframe_key_completion(self):
1173 """Test dict key completion applies to pandas DataFrames"""
1173 """Test dict key completion applies to pandas DataFrames"""
1174 import pandas
1174 import pandas
1175
1175
1176 ip = get_ipython()
1176 ip = get_ipython()
1177 complete = ip.Completer.complete
1177 complete = ip.Completer.complete
1178 ip.user_ns["d"] = pandas.DataFrame({"hello": [1], "world": [2]})
1178 ip.user_ns["d"] = pandas.DataFrame({"hello": [1], "world": [2]})
1179 _, matches = complete(line_buffer="d['")
1179 _, matches = complete(line_buffer="d['")
1180 self.assertIn("hello", matches)
1180 self.assertIn("hello", matches)
1181 self.assertIn("world", matches)
1181 self.assertIn("world", matches)
1182
1182
1183 def test_dict_key_completion_invalids(self):
1183 def test_dict_key_completion_invalids(self):
1184 """Smoke test cases dict key completion can't handle"""
1184 """Smoke test cases dict key completion can't handle"""
1185 ip = get_ipython()
1185 ip = get_ipython()
1186 complete = ip.Completer.complete
1186 complete = ip.Completer.complete
1187
1187
1188 ip.user_ns["no_getitem"] = None
1188 ip.user_ns["no_getitem"] = None
1189 ip.user_ns["no_keys"] = []
1189 ip.user_ns["no_keys"] = []
1190 ip.user_ns["cant_call_keys"] = dict
1190 ip.user_ns["cant_call_keys"] = dict
1191 ip.user_ns["empty"] = {}
1191 ip.user_ns["empty"] = {}
1192 ip.user_ns["d"] = {"abc": 5}
1192 ip.user_ns["d"] = {"abc": 5}
1193
1193
1194 _, matches = complete(line_buffer="no_getitem['")
1194 _, matches = complete(line_buffer="no_getitem['")
1195 _, matches = complete(line_buffer="no_keys['")
1195 _, matches = complete(line_buffer="no_keys['")
1196 _, matches = complete(line_buffer="cant_call_keys['")
1196 _, matches = complete(line_buffer="cant_call_keys['")
1197 _, matches = complete(line_buffer="empty['")
1197 _, matches = complete(line_buffer="empty['")
1198 _, matches = complete(line_buffer="name_error['")
1198 _, matches = complete(line_buffer="name_error['")
1199 _, matches = complete(line_buffer="d['\\") # incomplete escape
1199 _, matches = complete(line_buffer="d['\\") # incomplete escape
1200
1200
1201 def test_object_key_completion(self):
1201 def test_object_key_completion(self):
1202 ip = get_ipython()
1202 ip = get_ipython()
1203 ip.user_ns["key_completable"] = KeyCompletable(["qwerty", "qwick"])
1203 ip.user_ns["key_completable"] = KeyCompletable(["qwerty", "qwick"])
1204
1204
1205 _, matches = ip.Completer.complete(line_buffer="key_completable['qw")
1205 _, matches = ip.Completer.complete(line_buffer="key_completable['qw")
1206 self.assertIn("qwerty", matches)
1206 self.assertIn("qwerty", matches)
1207 self.assertIn("qwick", matches)
1207 self.assertIn("qwick", matches)
1208
1208
1209 def test_class_key_completion(self):
1209 def test_class_key_completion(self):
1210 ip = get_ipython()
1210 ip = get_ipython()
1211 NamedInstanceClass("qwerty")
1211 NamedInstanceClass("qwerty")
1212 NamedInstanceClass("qwick")
1212 NamedInstanceClass("qwick")
1213 ip.user_ns["named_instance_class"] = NamedInstanceClass
1213 ip.user_ns["named_instance_class"] = NamedInstanceClass
1214
1214
1215 _, matches = ip.Completer.complete(line_buffer="named_instance_class['qw")
1215 _, matches = ip.Completer.complete(line_buffer="named_instance_class['qw")
1216 self.assertIn("qwerty", matches)
1216 self.assertIn("qwerty", matches)
1217 self.assertIn("qwick", matches)
1217 self.assertIn("qwick", matches)
1218
1218
1219 def test_tryimport(self):
1219 def test_tryimport(self):
1220 """
1220 """
1221 Test that try-import don't crash on trailing dot, and import modules before
1221 Test that try-import don't crash on trailing dot, and import modules before
1222 """
1222 """
1223 from IPython.core.completerlib import try_import
1223 from IPython.core.completerlib import try_import
1224
1224
1225 assert try_import("IPython.")
1225 assert try_import("IPython.")
1226
1226
1227 def test_aimport_module_completer(self):
1227 def test_aimport_module_completer(self):
1228 ip = get_ipython()
1228 ip = get_ipython()
1229 _, matches = ip.complete("i", "%aimport i")
1229 _, matches = ip.complete("i", "%aimport i")
1230 self.assertIn("io", matches)
1230 self.assertIn("io", matches)
1231 self.assertNotIn("int", matches)
1231 self.assertNotIn("int", matches)
1232
1232
1233 def test_nested_import_module_completer(self):
1233 def test_nested_import_module_completer(self):
1234 ip = get_ipython()
1234 ip = get_ipython()
1235 _, matches = ip.complete(None, "import IPython.co", 17)
1235 _, matches = ip.complete(None, "import IPython.co", 17)
1236 self.assertIn("IPython.core", matches)
1236 self.assertIn("IPython.core", matches)
1237 self.assertNotIn("import IPython.core", matches)
1237 self.assertNotIn("import IPython.core", matches)
1238 self.assertNotIn("IPython.display", matches)
1238 self.assertNotIn("IPython.display", matches)
1239
1239
1240 def test_import_module_completer(self):
1240 def test_import_module_completer(self):
1241 ip = get_ipython()
1241 ip = get_ipython()
1242 _, matches = ip.complete("i", "import i")
1242 _, matches = ip.complete("i", "import i")
1243 self.assertIn("io", matches)
1243 self.assertIn("io", matches)
1244 self.assertNotIn("int", matches)
1244 self.assertNotIn("int", matches)
1245
1245
1246 def test_from_module_completer(self):
1246 def test_from_module_completer(self):
1247 ip = get_ipython()
1247 ip = get_ipython()
1248 _, matches = ip.complete("B", "from io import B", 16)
1248 _, matches = ip.complete("B", "from io import B", 16)
1249 self.assertIn("BytesIO", matches)
1249 self.assertIn("BytesIO", matches)
1250 self.assertNotIn("BaseException", matches)
1250 self.assertNotIn("BaseException", matches)
1251
1251
1252 def test_snake_case_completion(self):
1252 def test_snake_case_completion(self):
1253 ip = get_ipython()
1253 ip = get_ipython()
1254 ip.Completer.use_jedi = False
1254 ip.Completer.use_jedi = False
1255 ip.user_ns["some_three"] = 3
1255 ip.user_ns["some_three"] = 3
1256 ip.user_ns["some_four"] = 4
1256 ip.user_ns["some_four"] = 4
1257 _, matches = ip.complete("s_", "print(s_f")
1257 _, matches = ip.complete("s_", "print(s_f")
1258 self.assertIn("some_three", matches)
1258 self.assertIn("some_three", matches)
1259 self.assertIn("some_four", matches)
1259 self.assertIn("some_four", matches)
1260
1260
1261 def test_mix_terms(self):
1261 def test_mix_terms(self):
1262 ip = get_ipython()
1262 ip = get_ipython()
1263 from textwrap import dedent
1263 from textwrap import dedent
1264
1264
1265 ip.Completer.use_jedi = False
1265 ip.Completer.use_jedi = False
1266 ip.ex(
1266 ip.ex(
1267 dedent(
1267 dedent(
1268 """
1268 """
1269 class Test:
1269 class Test:
1270 def meth(self, meth_arg1):
1270 def meth(self, meth_arg1):
1271 print("meth")
1271 print("meth")
1272
1272
1273 def meth_1(self, meth1_arg1, meth1_arg2):
1273 def meth_1(self, meth1_arg1, meth1_arg2):
1274 print("meth1")
1274 print("meth1")
1275
1275
1276 def meth_2(self, meth2_arg1, meth2_arg2):
1276 def meth_2(self, meth2_arg1, meth2_arg2):
1277 print("meth2")
1277 print("meth2")
1278 test = Test()
1278 test = Test()
1279 """
1279 """
1280 )
1280 )
1281 )
1281 )
1282 _, matches = ip.complete(None, "test.meth(")
1282 _, matches = ip.complete(None, "test.meth(")
1283 self.assertIn("meth_arg1=", matches)
1283 self.assertIn("meth_arg1=", matches)
1284 self.assertNotIn("meth2_arg1=", matches)
1284 self.assertNotIn("meth2_arg1=", matches)
@@ -1,1366 +1,1363 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for various magic functions.
2 """Tests for various magic functions."""
3
4 Needs to be run by nose (to make ipython session available).
5 """
6
3
7 import asyncio
4 import asyncio
8 import io
5 import io
9 import os
6 import os
10 import re
7 import re
11 import shlex
8 import shlex
12 import sys
9 import sys
13 import warnings
10 import warnings
14 from importlib import invalidate_caches
11 from importlib import invalidate_caches
15 from io import StringIO
12 from io import StringIO
16 from pathlib import Path
13 from pathlib import Path
17 from textwrap import dedent
14 from textwrap import dedent
18 from unittest import TestCase, mock
15 from unittest import TestCase, mock
19
16
20 import pytest
17 import pytest
21
18
22 from IPython import get_ipython
19 from IPython import get_ipython
23 from IPython.core import magic
20 from IPython.core import magic
24 from IPython.core.error import UsageError
21 from IPython.core.error import UsageError
25 from IPython.core.magic import (
22 from IPython.core.magic import (
26 Magics,
23 Magics,
27 cell_magic,
24 cell_magic,
28 line_magic,
25 line_magic,
29 magics_class,
26 magics_class,
30 register_cell_magic,
27 register_cell_magic,
31 register_line_magic,
28 register_line_magic,
32 )
29 )
33 from IPython.core.magics import code, execution, logging, osm, script
30 from IPython.core.magics import code, execution, logging, osm, script
34 from IPython.testing import decorators as dec
31 from IPython.testing import decorators as dec
35 from IPython.testing import tools as tt
32 from IPython.testing import tools as tt
36 from IPython.utils.io import capture_output
33 from IPython.utils.io import capture_output
37 from IPython.utils.process import find_cmd
34 from IPython.utils.process import find_cmd
38 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
35 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
39
36
40 from .test_debugger import PdbTestInput
37 from .test_debugger import PdbTestInput
41
38
42
39
43 @magic.magics_class
40 @magic.magics_class
44 class DummyMagics(magic.Magics): pass
41 class DummyMagics(magic.Magics): pass
45
42
46 def test_extract_code_ranges():
43 def test_extract_code_ranges():
47 instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :"
44 instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :"
48 expected = [
45 expected = [
49 (0, 1),
46 (0, 1),
50 (2, 3),
47 (2, 3),
51 (4, 6),
48 (4, 6),
52 (6, 9),
49 (6, 9),
53 (9, 14),
50 (9, 14),
54 (16, None),
51 (16, None),
55 (None, 9),
52 (None, 9),
56 (9, None),
53 (9, None),
57 (None, 13),
54 (None, 13),
58 (None, None),
55 (None, None),
59 ]
56 ]
60 actual = list(code.extract_code_ranges(instr))
57 actual = list(code.extract_code_ranges(instr))
61 assert actual == expected
58 assert actual == expected
62
59
63 def test_extract_symbols():
60 def test_extract_symbols():
64 source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n"""
61 source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n"""
65 symbols_args = ["a", "b", "A", "A,b", "A,a", "z"]
62 symbols_args = ["a", "b", "A", "A,b", "A,a", "z"]
66 expected = [([], ['a']),
63 expected = [([], ['a']),
67 (["def b():\n return 42\n"], []),
64 (["def b():\n return 42\n"], []),
68 (["class A: pass\n"], []),
65 (["class A: pass\n"], []),
69 (["class A: pass\n", "def b():\n return 42\n"], []),
66 (["class A: pass\n", "def b():\n return 42\n"], []),
70 (["class A: pass\n"], ['a']),
67 (["class A: pass\n"], ['a']),
71 ([], ['z'])]
68 ([], ['z'])]
72 for symbols, exp in zip(symbols_args, expected):
69 for symbols, exp in zip(symbols_args, expected):
73 assert code.extract_symbols(source, symbols) == exp
70 assert code.extract_symbols(source, symbols) == exp
74
71
75
72
76 def test_extract_symbols_raises_exception_with_non_python_code():
73 def test_extract_symbols_raises_exception_with_non_python_code():
77 source = ("=begin A Ruby program :)=end\n"
74 source = ("=begin A Ruby program :)=end\n"
78 "def hello\n"
75 "def hello\n"
79 "puts 'Hello world'\n"
76 "puts 'Hello world'\n"
80 "end")
77 "end")
81 with pytest.raises(SyntaxError):
78 with pytest.raises(SyntaxError):
82 code.extract_symbols(source, "hello")
79 code.extract_symbols(source, "hello")
83
80
84
81
85 def test_magic_not_found():
82 def test_magic_not_found():
86 # magic not found raises UsageError
83 # magic not found raises UsageError
87 with pytest.raises(UsageError):
84 with pytest.raises(UsageError):
88 _ip.magic('doesntexist')
85 _ip.magic('doesntexist')
89
86
90 # ensure result isn't success when a magic isn't found
87 # ensure result isn't success when a magic isn't found
91 result = _ip.run_cell('%doesntexist')
88 result = _ip.run_cell('%doesntexist')
92 assert isinstance(result.error_in_exec, UsageError)
89 assert isinstance(result.error_in_exec, UsageError)
93
90
94
91
95 def test_cell_magic_not_found():
92 def test_cell_magic_not_found():
96 # magic not found raises UsageError
93 # magic not found raises UsageError
97 with pytest.raises(UsageError):
94 with pytest.raises(UsageError):
98 _ip.run_cell_magic('doesntexist', 'line', 'cell')
95 _ip.run_cell_magic('doesntexist', 'line', 'cell')
99
96
100 # ensure result isn't success when a magic isn't found
97 # ensure result isn't success when a magic isn't found
101 result = _ip.run_cell('%%doesntexist')
98 result = _ip.run_cell('%%doesntexist')
102 assert isinstance(result.error_in_exec, UsageError)
99 assert isinstance(result.error_in_exec, UsageError)
103
100
104
101
105 def test_magic_error_status():
102 def test_magic_error_status():
106 def fail(shell):
103 def fail(shell):
107 1/0
104 1/0
108 _ip.register_magic_function(fail)
105 _ip.register_magic_function(fail)
109 result = _ip.run_cell('%fail')
106 result = _ip.run_cell('%fail')
110 assert isinstance(result.error_in_exec, ZeroDivisionError)
107 assert isinstance(result.error_in_exec, ZeroDivisionError)
111
108
112
109
113 def test_config():
110 def test_config():
114 """ test that config magic does not raise
111 """ test that config magic does not raise
115 can happen if Configurable init is moved too early into
112 can happen if Configurable init is moved too early into
116 Magics.__init__ as then a Config object will be registered as a
113 Magics.__init__ as then a Config object will be registered as a
117 magic.
114 magic.
118 """
115 """
119 ## should not raise.
116 ## should not raise.
120 _ip.magic('config')
117 _ip.magic('config')
121
118
122 def test_config_available_configs():
119 def test_config_available_configs():
123 """ test that config magic prints available configs in unique and
120 """ test that config magic prints available configs in unique and
124 sorted order. """
121 sorted order. """
125 with capture_output() as captured:
122 with capture_output() as captured:
126 _ip.magic('config')
123 _ip.magic('config')
127
124
128 stdout = captured.stdout
125 stdout = captured.stdout
129 config_classes = stdout.strip().split('\n')[1:]
126 config_classes = stdout.strip().split('\n')[1:]
130 assert config_classes == sorted(set(config_classes))
127 assert config_classes == sorted(set(config_classes))
131
128
132 def test_config_print_class():
129 def test_config_print_class():
133 """ test that config with a classname prints the class's options. """
130 """ test that config with a classname prints the class's options. """
134 with capture_output() as captured:
131 with capture_output() as captured:
135 _ip.magic('config TerminalInteractiveShell')
132 _ip.magic('config TerminalInteractiveShell')
136
133
137 stdout = captured.stdout
134 stdout = captured.stdout
138 if not re.match("TerminalInteractiveShell.* options", stdout.splitlines()[0]):
135 if not re.match("TerminalInteractiveShell.* options", stdout.splitlines()[0]):
139 print(stdout)
136 print(stdout)
140 raise AssertionError("1st line of stdout not like "
137 raise AssertionError("1st line of stdout not like "
141 "'TerminalInteractiveShell.* options'")
138 "'TerminalInteractiveShell.* options'")
142
139
143 def test_rehashx():
140 def test_rehashx():
144 # clear up everything
141 # clear up everything
145 _ip.alias_manager.clear_aliases()
142 _ip.alias_manager.clear_aliases()
146 del _ip.db['syscmdlist']
143 del _ip.db['syscmdlist']
147
144
148 _ip.magic('rehashx')
145 _ip.magic('rehashx')
149 # Practically ALL ipython development systems will have more than 10 aliases
146 # Practically ALL ipython development systems will have more than 10 aliases
150
147
151 assert len(_ip.alias_manager.aliases) > 10
148 assert len(_ip.alias_manager.aliases) > 10
152 for name, cmd in _ip.alias_manager.aliases:
149 for name, cmd in _ip.alias_manager.aliases:
153 # we must strip dots from alias names
150 # we must strip dots from alias names
154 assert "." not in name
151 assert "." not in name
155
152
156 # rehashx must fill up syscmdlist
153 # rehashx must fill up syscmdlist
157 scoms = _ip.db['syscmdlist']
154 scoms = _ip.db['syscmdlist']
158 assert len(scoms) > 10
155 assert len(scoms) > 10
159
156
160
157
161 def test_magic_parse_options():
158 def test_magic_parse_options():
162 """Test that we don't mangle paths when parsing magic options."""
159 """Test that we don't mangle paths when parsing magic options."""
163 ip = get_ipython()
160 ip = get_ipython()
164 path = 'c:\\x'
161 path = 'c:\\x'
165 m = DummyMagics(ip)
162 m = DummyMagics(ip)
166 opts = m.parse_options('-f %s' % path,'f:')[0]
163 opts = m.parse_options('-f %s' % path,'f:')[0]
167 # argv splitting is os-dependent
164 # argv splitting is os-dependent
168 if os.name == 'posix':
165 if os.name == 'posix':
169 expected = 'c:x'
166 expected = 'c:x'
170 else:
167 else:
171 expected = path
168 expected = path
172 assert opts["f"] == expected
169 assert opts["f"] == expected
173
170
174
171
175 def test_magic_parse_long_options():
172 def test_magic_parse_long_options():
176 """Magic.parse_options can handle --foo=bar long options"""
173 """Magic.parse_options can handle --foo=bar long options"""
177 ip = get_ipython()
174 ip = get_ipython()
178 m = DummyMagics(ip)
175 m = DummyMagics(ip)
179 opts, _ = m.parse_options("--foo --bar=bubble", "a", "foo", "bar=")
176 opts, _ = m.parse_options("--foo --bar=bubble", "a", "foo", "bar=")
180 assert "foo" in opts
177 assert "foo" in opts
181 assert "bar" in opts
178 assert "bar" in opts
182 assert opts["bar"] == "bubble"
179 assert opts["bar"] == "bubble"
183
180
184
181
185 def doctest_hist_f():
182 def doctest_hist_f():
186 """Test %hist -f with temporary filename.
183 """Test %hist -f with temporary filename.
187
184
188 In [9]: import tempfile
185 In [9]: import tempfile
189
186
190 In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-')
187 In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-')
191
188
192 In [11]: %hist -nl -f $tfile 3
189 In [11]: %hist -nl -f $tfile 3
193
190
194 In [13]: import os; os.unlink(tfile)
191 In [13]: import os; os.unlink(tfile)
195 """
192 """
196
193
197
194
198 def doctest_hist_op():
195 def doctest_hist_op():
199 """Test %hist -op
196 """Test %hist -op
200
197
201 In [1]: class b(float):
198 In [1]: class b(float):
202 ...: pass
199 ...: pass
203 ...:
200 ...:
204
201
205 In [2]: class s(object):
202 In [2]: class s(object):
206 ...: def __str__(self):
203 ...: def __str__(self):
207 ...: return 's'
204 ...: return 's'
208 ...:
205 ...:
209
206
210 In [3]:
207 In [3]:
211
208
212 In [4]: class r(b):
209 In [4]: class r(b):
213 ...: def __repr__(self):
210 ...: def __repr__(self):
214 ...: return 'r'
211 ...: return 'r'
215 ...:
212 ...:
216
213
217 In [5]: class sr(s,r): pass
214 In [5]: class sr(s,r): pass
218 ...:
215 ...:
219
216
220 In [6]:
217 In [6]:
221
218
222 In [7]: bb=b()
219 In [7]: bb=b()
223
220
224 In [8]: ss=s()
221 In [8]: ss=s()
225
222
226 In [9]: rr=r()
223 In [9]: rr=r()
227
224
228 In [10]: ssrr=sr()
225 In [10]: ssrr=sr()
229
226
230 In [11]: 4.5
227 In [11]: 4.5
231 Out[11]: 4.5
228 Out[11]: 4.5
232
229
233 In [12]: str(ss)
230 In [12]: str(ss)
234 Out[12]: 's'
231 Out[12]: 's'
235
232
236 In [13]:
233 In [13]:
237
234
238 In [14]: %hist -op
235 In [14]: %hist -op
239 >>> class b:
236 >>> class b:
240 ... pass
237 ... pass
241 ...
238 ...
242 >>> class s(b):
239 >>> class s(b):
243 ... def __str__(self):
240 ... def __str__(self):
244 ... return 's'
241 ... return 's'
245 ...
242 ...
246 >>>
243 >>>
247 >>> class r(b):
244 >>> class r(b):
248 ... def __repr__(self):
245 ... def __repr__(self):
249 ... return 'r'
246 ... return 'r'
250 ...
247 ...
251 >>> class sr(s,r): pass
248 >>> class sr(s,r): pass
252 >>>
249 >>>
253 >>> bb=b()
250 >>> bb=b()
254 >>> ss=s()
251 >>> ss=s()
255 >>> rr=r()
252 >>> rr=r()
256 >>> ssrr=sr()
253 >>> ssrr=sr()
257 >>> 4.5
254 >>> 4.5
258 4.5
255 4.5
259 >>> str(ss)
256 >>> str(ss)
260 's'
257 's'
261 >>>
258 >>>
262 """
259 """
263
260
264 def test_hist_pof():
261 def test_hist_pof():
265 ip = get_ipython()
262 ip = get_ipython()
266 ip.run_cell("1+2", store_history=True)
263 ip.run_cell("1+2", store_history=True)
267 #raise Exception(ip.history_manager.session_number)
264 #raise Exception(ip.history_manager.session_number)
268 #raise Exception(list(ip.history_manager._get_range_session()))
265 #raise Exception(list(ip.history_manager._get_range_session()))
269 with TemporaryDirectory() as td:
266 with TemporaryDirectory() as td:
270 tf = os.path.join(td, 'hist.py')
267 tf = os.path.join(td, 'hist.py')
271 ip.run_line_magic('history', '-pof %s' % tf)
268 ip.run_line_magic('history', '-pof %s' % tf)
272 assert os.path.isfile(tf)
269 assert os.path.isfile(tf)
273
270
274
271
275 def test_macro():
272 def test_macro():
276 ip = get_ipython()
273 ip = get_ipython()
277 ip.history_manager.reset() # Clear any existing history.
274 ip.history_manager.reset() # Clear any existing history.
278 cmds = ["a=1", "def b():\n return a**2", "print(a,b())"]
275 cmds = ["a=1", "def b():\n return a**2", "print(a,b())"]
279 for i, cmd in enumerate(cmds, start=1):
276 for i, cmd in enumerate(cmds, start=1):
280 ip.history_manager.store_inputs(i, cmd)
277 ip.history_manager.store_inputs(i, cmd)
281 ip.magic("macro test 1-3")
278 ip.magic("macro test 1-3")
282 assert ip.user_ns["test"].value == "\n".join(cmds) + "\n"
279 assert ip.user_ns["test"].value == "\n".join(cmds) + "\n"
283
280
284 # List macros
281 # List macros
285 assert "test" in ip.magic("macro")
282 assert "test" in ip.magic("macro")
286
283
287
284
288 def test_macro_run():
285 def test_macro_run():
289 """Test that we can run a multi-line macro successfully."""
286 """Test that we can run a multi-line macro successfully."""
290 ip = get_ipython()
287 ip = get_ipython()
291 ip.history_manager.reset()
288 ip.history_manager.reset()
292 cmds = ["a=10", "a+=1", "print(a)", "%macro test 2-3"]
289 cmds = ["a=10", "a+=1", "print(a)", "%macro test 2-3"]
293 for cmd in cmds:
290 for cmd in cmds:
294 ip.run_cell(cmd, store_history=True)
291 ip.run_cell(cmd, store_history=True)
295 assert ip.user_ns["test"].value == "a+=1\nprint(a)\n"
292 assert ip.user_ns["test"].value == "a+=1\nprint(a)\n"
296 with tt.AssertPrints("12"):
293 with tt.AssertPrints("12"):
297 ip.run_cell("test")
294 ip.run_cell("test")
298 with tt.AssertPrints("13"):
295 with tt.AssertPrints("13"):
299 ip.run_cell("test")
296 ip.run_cell("test")
300
297
301
298
302 def test_magic_magic():
299 def test_magic_magic():
303 """Test %magic"""
300 """Test %magic"""
304 ip = get_ipython()
301 ip = get_ipython()
305 with capture_output() as captured:
302 with capture_output() as captured:
306 ip.magic("magic")
303 ip.magic("magic")
307
304
308 stdout = captured.stdout
305 stdout = captured.stdout
309 assert "%magic" in stdout
306 assert "%magic" in stdout
310 assert "IPython" in stdout
307 assert "IPython" in stdout
311 assert "Available" in stdout
308 assert "Available" in stdout
312
309
313
310
314 @dec.skipif_not_numpy
311 @dec.skipif_not_numpy
315 def test_numpy_reset_array_undec():
312 def test_numpy_reset_array_undec():
316 "Test '%reset array' functionality"
313 "Test '%reset array' functionality"
317 _ip.ex("import numpy as np")
314 _ip.ex("import numpy as np")
318 _ip.ex("a = np.empty(2)")
315 _ip.ex("a = np.empty(2)")
319 assert "a" in _ip.user_ns
316 assert "a" in _ip.user_ns
320 _ip.magic("reset -f array")
317 _ip.magic("reset -f array")
321 assert "a" not in _ip.user_ns
318 assert "a" not in _ip.user_ns
322
319
323
320
324 def test_reset_out():
321 def test_reset_out():
325 "Test '%reset out' magic"
322 "Test '%reset out' magic"
326 _ip.run_cell("parrot = 'dead'", store_history=True)
323 _ip.run_cell("parrot = 'dead'", store_history=True)
327 # test '%reset -f out', make an Out prompt
324 # test '%reset -f out', make an Out prompt
328 _ip.run_cell("parrot", store_history=True)
325 _ip.run_cell("parrot", store_history=True)
329 assert "dead" in [_ip.user_ns[x] for x in ("_", "__", "___")]
326 assert "dead" in [_ip.user_ns[x] for x in ("_", "__", "___")]
330 _ip.magic("reset -f out")
327 _ip.magic("reset -f out")
331 assert "dead" not in [_ip.user_ns[x] for x in ("_", "__", "___")]
328 assert "dead" not in [_ip.user_ns[x] for x in ("_", "__", "___")]
332 assert len(_ip.user_ns["Out"]) == 0
329 assert len(_ip.user_ns["Out"]) == 0
333
330
334
331
335 def test_reset_in():
332 def test_reset_in():
336 "Test '%reset in' magic"
333 "Test '%reset in' magic"
337 # test '%reset -f in'
334 # test '%reset -f in'
338 _ip.run_cell("parrot", store_history=True)
335 _ip.run_cell("parrot", store_history=True)
339 assert "parrot" in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
336 assert "parrot" in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
340 _ip.magic("%reset -f in")
337 _ip.magic("%reset -f in")
341 assert "parrot" not in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
338 assert "parrot" not in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
342 assert len(set(_ip.user_ns["In"])) == 1
339 assert len(set(_ip.user_ns["In"])) == 1
343
340
344
341
345 def test_reset_dhist():
342 def test_reset_dhist():
346 "Test '%reset dhist' magic"
343 "Test '%reset dhist' magic"
347 _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing
344 _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing
348 _ip.magic("cd " + os.path.dirname(pytest.__file__))
345 _ip.magic("cd " + os.path.dirname(pytest.__file__))
349 _ip.magic("cd -")
346 _ip.magic("cd -")
350 assert len(_ip.user_ns["_dh"]) > 0
347 assert len(_ip.user_ns["_dh"]) > 0
351 _ip.magic("reset -f dhist")
348 _ip.magic("reset -f dhist")
352 assert len(_ip.user_ns["_dh"]) == 0
349 assert len(_ip.user_ns["_dh"]) == 0
353 _ip.run_cell("_dh = [d for d in tmp]") # restore
350 _ip.run_cell("_dh = [d for d in tmp]") # restore
354
351
355
352
356 def test_reset_in_length():
353 def test_reset_in_length():
357 "Test that '%reset in' preserves In[] length"
354 "Test that '%reset in' preserves In[] length"
358 _ip.run_cell("print 'foo'")
355 _ip.run_cell("print 'foo'")
359 _ip.run_cell("reset -f in")
356 _ip.run_cell("reset -f in")
360 assert len(_ip.user_ns["In"]) == _ip.displayhook.prompt_count + 1
357 assert len(_ip.user_ns["In"]) == _ip.displayhook.prompt_count + 1
361
358
362
359
363 class TestResetErrors(TestCase):
360 class TestResetErrors(TestCase):
364
361
365 def test_reset_redefine(self):
362 def test_reset_redefine(self):
366
363
367 @magics_class
364 @magics_class
368 class KernelMagics(Magics):
365 class KernelMagics(Magics):
369 @line_magic
366 @line_magic
370 def less(self, shell): pass
367 def less(self, shell): pass
371
368
372 _ip.register_magics(KernelMagics)
369 _ip.register_magics(KernelMagics)
373
370
374 with self.assertLogs() as cm:
371 with self.assertLogs() as cm:
375 # hack, we want to just capture logs, but assertLogs fails if not
372 # hack, we want to just capture logs, but assertLogs fails if not
376 # logs get produce.
373 # logs get produce.
377 # so log one things we ignore.
374 # so log one things we ignore.
378 import logging as log_mod
375 import logging as log_mod
379 log = log_mod.getLogger()
376 log = log_mod.getLogger()
380 log.info('Nothing')
377 log.info('Nothing')
381 # end hack.
378 # end hack.
382 _ip.run_cell("reset -f")
379 _ip.run_cell("reset -f")
383
380
384 assert len(cm.output) == 1
381 assert len(cm.output) == 1
385 for out in cm.output:
382 for out in cm.output:
386 assert "Invalid alias" not in out
383 assert "Invalid alias" not in out
387
384
388 def test_tb_syntaxerror():
385 def test_tb_syntaxerror():
389 """test %tb after a SyntaxError"""
386 """test %tb after a SyntaxError"""
390 ip = get_ipython()
387 ip = get_ipython()
391 ip.run_cell("for")
388 ip.run_cell("for")
392
389
393 # trap and validate stdout
390 # trap and validate stdout
394 save_stdout = sys.stdout
391 save_stdout = sys.stdout
395 try:
392 try:
396 sys.stdout = StringIO()
393 sys.stdout = StringIO()
397 ip.run_cell("%tb")
394 ip.run_cell("%tb")
398 out = sys.stdout.getvalue()
395 out = sys.stdout.getvalue()
399 finally:
396 finally:
400 sys.stdout = save_stdout
397 sys.stdout = save_stdout
401 # trim output, and only check the last line
398 # trim output, and only check the last line
402 last_line = out.rstrip().splitlines()[-1].strip()
399 last_line = out.rstrip().splitlines()[-1].strip()
403 assert last_line == "SyntaxError: invalid syntax"
400 assert last_line == "SyntaxError: invalid syntax"
404
401
405
402
406 def test_time():
403 def test_time():
407 ip = get_ipython()
404 ip = get_ipython()
408
405
409 with tt.AssertPrints("Wall time: "):
406 with tt.AssertPrints("Wall time: "):
410 ip.run_cell("%time None")
407 ip.run_cell("%time None")
411
408
412 ip.run_cell("def f(kmjy):\n"
409 ip.run_cell("def f(kmjy):\n"
413 " %time print (2*kmjy)")
410 " %time print (2*kmjy)")
414
411
415 with tt.AssertPrints("Wall time: "):
412 with tt.AssertPrints("Wall time: "):
416 with tt.AssertPrints("hihi", suppress=False):
413 with tt.AssertPrints("hihi", suppress=False):
417 ip.run_cell("f('hi')")
414 ip.run_cell("f('hi')")
418
415
419 def test_time_last_not_expression():
416 def test_time_last_not_expression():
420 ip.run_cell("%%time\n"
417 ip.run_cell("%%time\n"
421 "var_1 = 1\n"
418 "var_1 = 1\n"
422 "var_2 = 2\n")
419 "var_2 = 2\n")
423 assert ip.user_ns['var_1'] == 1
420 assert ip.user_ns['var_1'] == 1
424 del ip.user_ns['var_1']
421 del ip.user_ns['var_1']
425 assert ip.user_ns['var_2'] == 2
422 assert ip.user_ns['var_2'] == 2
426 del ip.user_ns['var_2']
423 del ip.user_ns['var_2']
427
424
428
425
429 @dec.skip_win32
426 @dec.skip_win32
430 def test_time2():
427 def test_time2():
431 ip = get_ipython()
428 ip = get_ipython()
432
429
433 with tt.AssertPrints("CPU times: user "):
430 with tt.AssertPrints("CPU times: user "):
434 ip.run_cell("%time None")
431 ip.run_cell("%time None")
435
432
436 def test_time3():
433 def test_time3():
437 """Erroneous magic function calls, issue gh-3334"""
434 """Erroneous magic function calls, issue gh-3334"""
438 ip = get_ipython()
435 ip = get_ipython()
439 ip.user_ns.pop('run', None)
436 ip.user_ns.pop('run', None)
440
437
441 with tt.AssertNotPrints("not found", channel='stderr'):
438 with tt.AssertNotPrints("not found", channel='stderr'):
442 ip.run_cell("%%time\n"
439 ip.run_cell("%%time\n"
443 "run = 0\n"
440 "run = 0\n"
444 "run += 1")
441 "run += 1")
445
442
446 def test_multiline_time():
443 def test_multiline_time():
447 """Make sure last statement from time return a value."""
444 """Make sure last statement from time return a value."""
448 ip = get_ipython()
445 ip = get_ipython()
449 ip.user_ns.pop('run', None)
446 ip.user_ns.pop('run', None)
450
447
451 ip.run_cell(dedent("""\
448 ip.run_cell(dedent("""\
452 %%time
449 %%time
453 a = "ho"
450 a = "ho"
454 b = "hey"
451 b = "hey"
455 a+b
452 a+b
456 """
453 """
457 )
454 )
458 )
455 )
459 assert ip.user_ns_hidden["_"] == "hohey"
456 assert ip.user_ns_hidden["_"] == "hohey"
460
457
461
458
462 def test_time_local_ns():
459 def test_time_local_ns():
463 """
460 """
464 Test that local_ns is actually global_ns when running a cell magic
461 Test that local_ns is actually global_ns when running a cell magic
465 """
462 """
466 ip = get_ipython()
463 ip = get_ipython()
467 ip.run_cell("%%time\n" "myvar = 1")
464 ip.run_cell("%%time\n" "myvar = 1")
468 assert ip.user_ns["myvar"] == 1
465 assert ip.user_ns["myvar"] == 1
469 del ip.user_ns["myvar"]
466 del ip.user_ns["myvar"]
470
467
471
468
472 def test_doctest_mode():
469 def test_doctest_mode():
473 "Toggle doctest_mode twice, it should be a no-op and run without error"
470 "Toggle doctest_mode twice, it should be a no-op and run without error"
474 _ip.magic('doctest_mode')
471 _ip.magic('doctest_mode')
475 _ip.magic('doctest_mode')
472 _ip.magic('doctest_mode')
476
473
477
474
478 def test_parse_options():
475 def test_parse_options():
479 """Tests for basic options parsing in magics."""
476 """Tests for basic options parsing in magics."""
480 # These are only the most minimal of tests, more should be added later. At
477 # These are only the most minimal of tests, more should be added later. At
481 # the very least we check that basic text/unicode calls work OK.
478 # the very least we check that basic text/unicode calls work OK.
482 m = DummyMagics(_ip)
479 m = DummyMagics(_ip)
483 assert m.parse_options("foo", "")[1] == "foo"
480 assert m.parse_options("foo", "")[1] == "foo"
484 assert m.parse_options("foo", "")[1] == "foo"
481 assert m.parse_options("foo", "")[1] == "foo"
485
482
486
483
487 def test_parse_options_preserve_non_option_string():
484 def test_parse_options_preserve_non_option_string():
488 """Test to assert preservation of non-option part of magic-block, while parsing magic options."""
485 """Test to assert preservation of non-option part of magic-block, while parsing magic options."""
489 m = DummyMagics(_ip)
486 m = DummyMagics(_ip)
490 opts, stmt = m.parse_options(
487 opts, stmt = m.parse_options(
491 " -n1 -r 13 _ = 314 + foo", "n:r:", preserve_non_opts=True
488 " -n1 -r 13 _ = 314 + foo", "n:r:", preserve_non_opts=True
492 )
489 )
493 assert opts == {"n": "1", "r": "13"}
490 assert opts == {"n": "1", "r": "13"}
494 assert stmt == "_ = 314 + foo"
491 assert stmt == "_ = 314 + foo"
495
492
496
493
497 def test_run_magic_preserve_code_block():
494 def test_run_magic_preserve_code_block():
498 """Test to assert preservation of non-option part of magic-block, while running magic."""
495 """Test to assert preservation of non-option part of magic-block, while running magic."""
499 _ip.user_ns["spaces"] = []
496 _ip.user_ns["spaces"] = []
500 _ip.magic("timeit -n1 -r1 spaces.append([s.count(' ') for s in ['document']])")
497 _ip.magic("timeit -n1 -r1 spaces.append([s.count(' ') for s in ['document']])")
501 assert _ip.user_ns["spaces"] == [[0]]
498 assert _ip.user_ns["spaces"] == [[0]]
502
499
503
500
504 def test_dirops():
501 def test_dirops():
505 """Test various directory handling operations."""
502 """Test various directory handling operations."""
506 # curpath = lambda :os.path.splitdrive(os.getcwd())[1].replace('\\','/')
503 # curpath = lambda :os.path.splitdrive(os.getcwd())[1].replace('\\','/')
507 curpath = os.getcwd
504 curpath = os.getcwd
508 startdir = os.getcwd()
505 startdir = os.getcwd()
509 ipdir = os.path.realpath(_ip.ipython_dir)
506 ipdir = os.path.realpath(_ip.ipython_dir)
510 try:
507 try:
511 _ip.magic('cd "%s"' % ipdir)
508 _ip.magic('cd "%s"' % ipdir)
512 assert curpath() == ipdir
509 assert curpath() == ipdir
513 _ip.magic('cd -')
510 _ip.magic('cd -')
514 assert curpath() == startdir
511 assert curpath() == startdir
515 _ip.magic('pushd "%s"' % ipdir)
512 _ip.magic('pushd "%s"' % ipdir)
516 assert curpath() == ipdir
513 assert curpath() == ipdir
517 _ip.magic('popd')
514 _ip.magic('popd')
518 assert curpath() == startdir
515 assert curpath() == startdir
519 finally:
516 finally:
520 os.chdir(startdir)
517 os.chdir(startdir)
521
518
522
519
523 def test_cd_force_quiet():
520 def test_cd_force_quiet():
524 """Test OSMagics.cd_force_quiet option"""
521 """Test OSMagics.cd_force_quiet option"""
525 _ip.config.OSMagics.cd_force_quiet = True
522 _ip.config.OSMagics.cd_force_quiet = True
526 osmagics = osm.OSMagics(shell=_ip)
523 osmagics = osm.OSMagics(shell=_ip)
527
524
528 startdir = os.getcwd()
525 startdir = os.getcwd()
529 ipdir = os.path.realpath(_ip.ipython_dir)
526 ipdir = os.path.realpath(_ip.ipython_dir)
530
527
531 try:
528 try:
532 with tt.AssertNotPrints(ipdir):
529 with tt.AssertNotPrints(ipdir):
533 osmagics.cd('"%s"' % ipdir)
530 osmagics.cd('"%s"' % ipdir)
534 with tt.AssertNotPrints(startdir):
531 with tt.AssertNotPrints(startdir):
535 osmagics.cd('-')
532 osmagics.cd('-')
536 finally:
533 finally:
537 os.chdir(startdir)
534 os.chdir(startdir)
538
535
539
536
540 def test_xmode():
537 def test_xmode():
541 # Calling xmode three times should be a no-op
538 # Calling xmode three times should be a no-op
542 xmode = _ip.InteractiveTB.mode
539 xmode = _ip.InteractiveTB.mode
543 for i in range(4):
540 for i in range(4):
544 _ip.magic("xmode")
541 _ip.magic("xmode")
545 assert _ip.InteractiveTB.mode == xmode
542 assert _ip.InteractiveTB.mode == xmode
546
543
547 def test_reset_hard():
544 def test_reset_hard():
548 monitor = []
545 monitor = []
549 class A(object):
546 class A(object):
550 def __del__(self):
547 def __del__(self):
551 monitor.append(1)
548 monitor.append(1)
552 def __repr__(self):
549 def __repr__(self):
553 return "<A instance>"
550 return "<A instance>"
554
551
555 _ip.user_ns["a"] = A()
552 _ip.user_ns["a"] = A()
556 _ip.run_cell("a")
553 _ip.run_cell("a")
557
554
558 assert monitor == []
555 assert monitor == []
559 _ip.magic("reset -f")
556 _ip.magic("reset -f")
560 assert monitor == [1]
557 assert monitor == [1]
561
558
562 class TestXdel(tt.TempFileMixin):
559 class TestXdel(tt.TempFileMixin):
563 def test_xdel(self):
560 def test_xdel(self):
564 """Test that references from %run are cleared by xdel."""
561 """Test that references from %run are cleared by xdel."""
565 src = ("class A(object):\n"
562 src = ("class A(object):\n"
566 " monitor = []\n"
563 " monitor = []\n"
567 " def __del__(self):\n"
564 " def __del__(self):\n"
568 " self.monitor.append(1)\n"
565 " self.monitor.append(1)\n"
569 "a = A()\n")
566 "a = A()\n")
570 self.mktmp(src)
567 self.mktmp(src)
571 # %run creates some hidden references...
568 # %run creates some hidden references...
572 _ip.magic("run %s" % self.fname)
569 _ip.magic("run %s" % self.fname)
573 # ... as does the displayhook.
570 # ... as does the displayhook.
574 _ip.run_cell("a")
571 _ip.run_cell("a")
575
572
576 monitor = _ip.user_ns["A"].monitor
573 monitor = _ip.user_ns["A"].monitor
577 assert monitor == []
574 assert monitor == []
578
575
579 _ip.magic("xdel a")
576 _ip.magic("xdel a")
580
577
581 # Check that a's __del__ method has been called.
578 # Check that a's __del__ method has been called.
582 assert monitor == [1]
579 assert monitor == [1]
583
580
584 def doctest_who():
581 def doctest_who():
585 """doctest for %who
582 """doctest for %who
586
583
587 In [1]: %reset -sf
584 In [1]: %reset -sf
588
585
589 In [2]: alpha = 123
586 In [2]: alpha = 123
590
587
591 In [3]: beta = 'beta'
588 In [3]: beta = 'beta'
592
589
593 In [4]: %who int
590 In [4]: %who int
594 alpha
591 alpha
595
592
596 In [5]: %who str
593 In [5]: %who str
597 beta
594 beta
598
595
599 In [6]: %whos
596 In [6]: %whos
600 Variable Type Data/Info
597 Variable Type Data/Info
601 ----------------------------
598 ----------------------------
602 alpha int 123
599 alpha int 123
603 beta str beta
600 beta str beta
604
601
605 In [7]: %who_ls
602 In [7]: %who_ls
606 Out[7]: ['alpha', 'beta']
603 Out[7]: ['alpha', 'beta']
607 """
604 """
608
605
609 def test_whos():
606 def test_whos():
610 """Check that whos is protected against objects where repr() fails."""
607 """Check that whos is protected against objects where repr() fails."""
611 class A(object):
608 class A(object):
612 def __repr__(self):
609 def __repr__(self):
613 raise Exception()
610 raise Exception()
614 _ip.user_ns['a'] = A()
611 _ip.user_ns['a'] = A()
615 _ip.magic("whos")
612 _ip.magic("whos")
616
613
617 def doctest_precision():
614 def doctest_precision():
618 """doctest for %precision
615 """doctest for %precision
619
616
620 In [1]: f = get_ipython().display_formatter.formatters['text/plain']
617 In [1]: f = get_ipython().display_formatter.formatters['text/plain']
621
618
622 In [2]: %precision 5
619 In [2]: %precision 5
623 Out[2]: '%.5f'
620 Out[2]: '%.5f'
624
621
625 In [3]: f.float_format
622 In [3]: f.float_format
626 Out[3]: '%.5f'
623 Out[3]: '%.5f'
627
624
628 In [4]: %precision %e
625 In [4]: %precision %e
629 Out[4]: '%e'
626 Out[4]: '%e'
630
627
631 In [5]: f(3.1415927)
628 In [5]: f(3.1415927)
632 Out[5]: '3.141593e+00'
629 Out[5]: '3.141593e+00'
633 """
630 """
634
631
635 def test_debug_magic():
632 def test_debug_magic():
636 """Test debugging a small code with %debug
633 """Test debugging a small code with %debug
637
634
638 In [1]: with PdbTestInput(['c']):
635 In [1]: with PdbTestInput(['c']):
639 ...: %debug print("a b") #doctest: +ELLIPSIS
636 ...: %debug print("a b") #doctest: +ELLIPSIS
640 ...:
637 ...:
641 ...
638 ...
642 ipdb> c
639 ipdb> c
643 a b
640 a b
644 In [2]:
641 In [2]:
645 """
642 """
646
643
647 def test_psearch():
644 def test_psearch():
648 with tt.AssertPrints("dict.fromkeys"):
645 with tt.AssertPrints("dict.fromkeys"):
649 _ip.run_cell("dict.fr*?")
646 _ip.run_cell("dict.fr*?")
650 with tt.AssertPrints("π.is_integer"):
647 with tt.AssertPrints("π.is_integer"):
651 _ip.run_cell("π = 3.14;\nπ.is_integ*?")
648 _ip.run_cell("π = 3.14;\nπ.is_integ*?")
652
649
653 def test_timeit_shlex():
650 def test_timeit_shlex():
654 """test shlex issues with timeit (#1109)"""
651 """test shlex issues with timeit (#1109)"""
655 _ip.ex("def f(*a,**kw): pass")
652 _ip.ex("def f(*a,**kw): pass")
656 _ip.magic('timeit -n1 "this is a bug".count(" ")')
653 _ip.magic('timeit -n1 "this is a bug".count(" ")')
657 _ip.magic('timeit -r1 -n1 f(" ", 1)')
654 _ip.magic('timeit -r1 -n1 f(" ", 1)')
658 _ip.magic('timeit -r1 -n1 f(" ", 1, " ", 2, " ")')
655 _ip.magic('timeit -r1 -n1 f(" ", 1, " ", 2, " ")')
659 _ip.magic('timeit -r1 -n1 ("a " + "b")')
656 _ip.magic('timeit -r1 -n1 ("a " + "b")')
660 _ip.magic('timeit -r1 -n1 f("a " + "b")')
657 _ip.magic('timeit -r1 -n1 f("a " + "b")')
661 _ip.magic('timeit -r1 -n1 f("a " + "b ")')
658 _ip.magic('timeit -r1 -n1 f("a " + "b ")')
662
659
663
660
664 def test_timeit_special_syntax():
661 def test_timeit_special_syntax():
665 "Test %%timeit with IPython special syntax"
662 "Test %%timeit with IPython special syntax"
666 @register_line_magic
663 @register_line_magic
667 def lmagic(line):
664 def lmagic(line):
668 ip = get_ipython()
665 ip = get_ipython()
669 ip.user_ns['lmagic_out'] = line
666 ip.user_ns['lmagic_out'] = line
670
667
671 # line mode test
668 # line mode test
672 _ip.run_line_magic("timeit", "-n1 -r1 %lmagic my line")
669 _ip.run_line_magic("timeit", "-n1 -r1 %lmagic my line")
673 assert _ip.user_ns["lmagic_out"] == "my line"
670 assert _ip.user_ns["lmagic_out"] == "my line"
674 # cell mode test
671 # cell mode test
675 _ip.run_cell_magic("timeit", "-n1 -r1", "%lmagic my line2")
672 _ip.run_cell_magic("timeit", "-n1 -r1", "%lmagic my line2")
676 assert _ip.user_ns["lmagic_out"] == "my line2"
673 assert _ip.user_ns["lmagic_out"] == "my line2"
677
674
678
675
679 def test_timeit_return():
676 def test_timeit_return():
680 """
677 """
681 test whether timeit -o return object
678 test whether timeit -o return object
682 """
679 """
683
680
684 res = _ip.run_line_magic('timeit','-n10 -r10 -o 1')
681 res = _ip.run_line_magic('timeit','-n10 -r10 -o 1')
685 assert(res is not None)
682 assert(res is not None)
686
683
687 def test_timeit_quiet():
684 def test_timeit_quiet():
688 """
685 """
689 test quiet option of timeit magic
686 test quiet option of timeit magic
690 """
687 """
691 with tt.AssertNotPrints("loops"):
688 with tt.AssertNotPrints("loops"):
692 _ip.run_cell("%timeit -n1 -r1 -q 1")
689 _ip.run_cell("%timeit -n1 -r1 -q 1")
693
690
694 def test_timeit_return_quiet():
691 def test_timeit_return_quiet():
695 with tt.AssertNotPrints("loops"):
692 with tt.AssertNotPrints("loops"):
696 res = _ip.run_line_magic('timeit', '-n1 -r1 -q -o 1')
693 res = _ip.run_line_magic('timeit', '-n1 -r1 -q -o 1')
697 assert (res is not None)
694 assert (res is not None)
698
695
699 def test_timeit_invalid_return():
696 def test_timeit_invalid_return():
700 with pytest.raises(SyntaxError):
697 with pytest.raises(SyntaxError):
701 _ip.run_line_magic('timeit', 'return')
698 _ip.run_line_magic('timeit', 'return')
702
699
703 @dec.skipif(execution.profile is None)
700 @dec.skipif(execution.profile is None)
704 def test_prun_special_syntax():
701 def test_prun_special_syntax():
705 "Test %%prun with IPython special syntax"
702 "Test %%prun with IPython special syntax"
706 @register_line_magic
703 @register_line_magic
707 def lmagic(line):
704 def lmagic(line):
708 ip = get_ipython()
705 ip = get_ipython()
709 ip.user_ns['lmagic_out'] = line
706 ip.user_ns['lmagic_out'] = line
710
707
711 # line mode test
708 # line mode test
712 _ip.run_line_magic("prun", "-q %lmagic my line")
709 _ip.run_line_magic("prun", "-q %lmagic my line")
713 assert _ip.user_ns["lmagic_out"] == "my line"
710 assert _ip.user_ns["lmagic_out"] == "my line"
714 # cell mode test
711 # cell mode test
715 _ip.run_cell_magic("prun", "-q", "%lmagic my line2")
712 _ip.run_cell_magic("prun", "-q", "%lmagic my line2")
716 assert _ip.user_ns["lmagic_out"] == "my line2"
713 assert _ip.user_ns["lmagic_out"] == "my line2"
717
714
718
715
719 @dec.skipif(execution.profile is None)
716 @dec.skipif(execution.profile is None)
720 def test_prun_quotes():
717 def test_prun_quotes():
721 "Test that prun does not clobber string escapes (GH #1302)"
718 "Test that prun does not clobber string escapes (GH #1302)"
722 _ip.magic(r"prun -q x = '\t'")
719 _ip.magic(r"prun -q x = '\t'")
723 assert _ip.user_ns["x"] == "\t"
720 assert _ip.user_ns["x"] == "\t"
724
721
725
722
726 def test_extension():
723 def test_extension():
727 # Debugging information for failures of this test
724 # Debugging information for failures of this test
728 print('sys.path:')
725 print('sys.path:')
729 for p in sys.path:
726 for p in sys.path:
730 print(' ', p)
727 print(' ', p)
731 print('CWD', os.getcwd())
728 print('CWD', os.getcwd())
732
729
733 pytest.raises(ImportError, _ip.magic, "load_ext daft_extension")
730 pytest.raises(ImportError, _ip.magic, "load_ext daft_extension")
734 daft_path = os.path.join(os.path.dirname(__file__), "daft_extension")
731 daft_path = os.path.join(os.path.dirname(__file__), "daft_extension")
735 sys.path.insert(0, daft_path)
732 sys.path.insert(0, daft_path)
736 try:
733 try:
737 _ip.user_ns.pop('arq', None)
734 _ip.user_ns.pop('arq', None)
738 invalidate_caches() # Clear import caches
735 invalidate_caches() # Clear import caches
739 _ip.magic("load_ext daft_extension")
736 _ip.magic("load_ext daft_extension")
740 assert _ip.user_ns["arq"] == 185
737 assert _ip.user_ns["arq"] == 185
741 _ip.magic("unload_ext daft_extension")
738 _ip.magic("unload_ext daft_extension")
742 assert 'arq' not in _ip.user_ns
739 assert 'arq' not in _ip.user_ns
743 finally:
740 finally:
744 sys.path.remove(daft_path)
741 sys.path.remove(daft_path)
745
742
746
743
747 def test_notebook_export_json():
744 def test_notebook_export_json():
748 _ip = get_ipython()
745 _ip = get_ipython()
749 _ip.history_manager.reset() # Clear any existing history.
746 _ip.history_manager.reset() # Clear any existing history.
750 cmds = ["a=1", "def b():\n return a**2", "print('noël, été', b())"]
747 cmds = ["a=1", "def b():\n return a**2", "print('noël, été', b())"]
751 for i, cmd in enumerate(cmds, start=1):
748 for i, cmd in enumerate(cmds, start=1):
752 _ip.history_manager.store_inputs(i, cmd)
749 _ip.history_manager.store_inputs(i, cmd)
753 with TemporaryDirectory() as td:
750 with TemporaryDirectory() as td:
754 outfile = os.path.join(td, "nb.ipynb")
751 outfile = os.path.join(td, "nb.ipynb")
755 _ip.magic("notebook -e %s" % outfile)
752 _ip.magic("notebook -e %s" % outfile)
756
753
757
754
758 class TestEnv(TestCase):
755 class TestEnv(TestCase):
759
756
760 def test_env(self):
757 def test_env(self):
761 env = _ip.magic("env")
758 env = _ip.magic("env")
762 self.assertTrue(isinstance(env, dict))
759 self.assertTrue(isinstance(env, dict))
763
760
764 def test_env_secret(self):
761 def test_env_secret(self):
765 env = _ip.magic("env")
762 env = _ip.magic("env")
766 hidden = "<hidden>"
763 hidden = "<hidden>"
767 with mock.patch.dict(
764 with mock.patch.dict(
768 os.environ,
765 os.environ,
769 {
766 {
770 "API_KEY": "abc123",
767 "API_KEY": "abc123",
771 "SECRET_THING": "ssshhh",
768 "SECRET_THING": "ssshhh",
772 "JUPYTER_TOKEN": "",
769 "JUPYTER_TOKEN": "",
773 "VAR": "abc"
770 "VAR": "abc"
774 }
771 }
775 ):
772 ):
776 env = _ip.magic("env")
773 env = _ip.magic("env")
777 assert env["API_KEY"] == hidden
774 assert env["API_KEY"] == hidden
778 assert env["SECRET_THING"] == hidden
775 assert env["SECRET_THING"] == hidden
779 assert env["JUPYTER_TOKEN"] == hidden
776 assert env["JUPYTER_TOKEN"] == hidden
780 assert env["VAR"] == "abc"
777 assert env["VAR"] == "abc"
781
778
782 def test_env_get_set_simple(self):
779 def test_env_get_set_simple(self):
783 env = _ip.magic("env var val1")
780 env = _ip.magic("env var val1")
784 self.assertEqual(env, None)
781 self.assertEqual(env, None)
785 self.assertEqual(os.environ['var'], 'val1')
782 self.assertEqual(os.environ['var'], 'val1')
786 self.assertEqual(_ip.magic("env var"), 'val1')
783 self.assertEqual(_ip.magic("env var"), 'val1')
787 env = _ip.magic("env var=val2")
784 env = _ip.magic("env var=val2")
788 self.assertEqual(env, None)
785 self.assertEqual(env, None)
789 self.assertEqual(os.environ['var'], 'val2')
786 self.assertEqual(os.environ['var'], 'val2')
790
787
791 def test_env_get_set_complex(self):
788 def test_env_get_set_complex(self):
792 env = _ip.magic("env var 'val1 '' 'val2")
789 env = _ip.magic("env var 'val1 '' 'val2")
793 self.assertEqual(env, None)
790 self.assertEqual(env, None)
794 self.assertEqual(os.environ['var'], "'val1 '' 'val2")
791 self.assertEqual(os.environ['var'], "'val1 '' 'val2")
795 self.assertEqual(_ip.magic("env var"), "'val1 '' 'val2")
792 self.assertEqual(_ip.magic("env var"), "'val1 '' 'val2")
796 env = _ip.magic('env var=val2 val3="val4')
793 env = _ip.magic('env var=val2 val3="val4')
797 self.assertEqual(env, None)
794 self.assertEqual(env, None)
798 self.assertEqual(os.environ['var'], 'val2 val3="val4')
795 self.assertEqual(os.environ['var'], 'val2 val3="val4')
799
796
800 def test_env_set_bad_input(self):
797 def test_env_set_bad_input(self):
801 self.assertRaises(UsageError, lambda: _ip.magic("set_env var"))
798 self.assertRaises(UsageError, lambda: _ip.magic("set_env var"))
802
799
803 def test_env_set_whitespace(self):
800 def test_env_set_whitespace(self):
804 self.assertRaises(UsageError, lambda: _ip.magic("env var A=B"))
801 self.assertRaises(UsageError, lambda: _ip.magic("env var A=B"))
805
802
806
803
807 class CellMagicTestCase(TestCase):
804 class CellMagicTestCase(TestCase):
808
805
809 def check_ident(self, magic):
806 def check_ident(self, magic):
810 # Manually called, we get the result
807 # Manually called, we get the result
811 out = _ip.run_cell_magic(magic, "a", "b")
808 out = _ip.run_cell_magic(magic, "a", "b")
812 assert out == ("a", "b")
809 assert out == ("a", "b")
813 # Via run_cell, it goes into the user's namespace via displayhook
810 # Via run_cell, it goes into the user's namespace via displayhook
814 _ip.run_cell("%%" + magic + " c\nd\n")
811 _ip.run_cell("%%" + magic + " c\nd\n")
815 assert _ip.user_ns["_"] == ("c", "d\n")
812 assert _ip.user_ns["_"] == ("c", "d\n")
816
813
817 def test_cell_magic_func_deco(self):
814 def test_cell_magic_func_deco(self):
818 "Cell magic using simple decorator"
815 "Cell magic using simple decorator"
819 @register_cell_magic
816 @register_cell_magic
820 def cellm(line, cell):
817 def cellm(line, cell):
821 return line, cell
818 return line, cell
822
819
823 self.check_ident('cellm')
820 self.check_ident('cellm')
824
821
825 def test_cell_magic_reg(self):
822 def test_cell_magic_reg(self):
826 "Cell magic manually registered"
823 "Cell magic manually registered"
827 def cellm(line, cell):
824 def cellm(line, cell):
828 return line, cell
825 return line, cell
829
826
830 _ip.register_magic_function(cellm, 'cell', 'cellm2')
827 _ip.register_magic_function(cellm, 'cell', 'cellm2')
831 self.check_ident('cellm2')
828 self.check_ident('cellm2')
832
829
833 def test_cell_magic_class(self):
830 def test_cell_magic_class(self):
834 "Cell magics declared via a class"
831 "Cell magics declared via a class"
835 @magics_class
832 @magics_class
836 class MyMagics(Magics):
833 class MyMagics(Magics):
837
834
838 @cell_magic
835 @cell_magic
839 def cellm3(self, line, cell):
836 def cellm3(self, line, cell):
840 return line, cell
837 return line, cell
841
838
842 _ip.register_magics(MyMagics)
839 _ip.register_magics(MyMagics)
843 self.check_ident('cellm3')
840 self.check_ident('cellm3')
844
841
845 def test_cell_magic_class2(self):
842 def test_cell_magic_class2(self):
846 "Cell magics declared via a class, #2"
843 "Cell magics declared via a class, #2"
847 @magics_class
844 @magics_class
848 class MyMagics2(Magics):
845 class MyMagics2(Magics):
849
846
850 @cell_magic('cellm4')
847 @cell_magic('cellm4')
851 def cellm33(self, line, cell):
848 def cellm33(self, line, cell):
852 return line, cell
849 return line, cell
853
850
854 _ip.register_magics(MyMagics2)
851 _ip.register_magics(MyMagics2)
855 self.check_ident('cellm4')
852 self.check_ident('cellm4')
856 # Check that nothing is registered as 'cellm33'
853 # Check that nothing is registered as 'cellm33'
857 c33 = _ip.find_cell_magic('cellm33')
854 c33 = _ip.find_cell_magic('cellm33')
858 assert c33 == None
855 assert c33 == None
859
856
860 def test_file():
857 def test_file():
861 """Basic %%writefile"""
858 """Basic %%writefile"""
862 ip = get_ipython()
859 ip = get_ipython()
863 with TemporaryDirectory() as td:
860 with TemporaryDirectory() as td:
864 fname = os.path.join(td, 'file1')
861 fname = os.path.join(td, 'file1')
865 ip.run_cell_magic("writefile", fname, u'\n'.join([
862 ip.run_cell_magic("writefile", fname, u'\n'.join([
866 'line1',
863 'line1',
867 'line2',
864 'line2',
868 ]))
865 ]))
869 s = Path(fname).read_text()
866 s = Path(fname).read_text()
870 assert "line1\n" in s
867 assert "line1\n" in s
871 assert "line2" in s
868 assert "line2" in s
872
869
873
870
874 @dec.skip_win32
871 @dec.skip_win32
875 def test_file_single_quote():
872 def test_file_single_quote():
876 """Basic %%writefile with embedded single quotes"""
873 """Basic %%writefile with embedded single quotes"""
877 ip = get_ipython()
874 ip = get_ipython()
878 with TemporaryDirectory() as td:
875 with TemporaryDirectory() as td:
879 fname = os.path.join(td, '\'file1\'')
876 fname = os.path.join(td, '\'file1\'')
880 ip.run_cell_magic("writefile", fname, u'\n'.join([
877 ip.run_cell_magic("writefile", fname, u'\n'.join([
881 'line1',
878 'line1',
882 'line2',
879 'line2',
883 ]))
880 ]))
884 s = Path(fname).read_text()
881 s = Path(fname).read_text()
885 assert "line1\n" in s
882 assert "line1\n" in s
886 assert "line2" in s
883 assert "line2" in s
887
884
888
885
889 @dec.skip_win32
886 @dec.skip_win32
890 def test_file_double_quote():
887 def test_file_double_quote():
891 """Basic %%writefile with embedded double quotes"""
888 """Basic %%writefile with embedded double quotes"""
892 ip = get_ipython()
889 ip = get_ipython()
893 with TemporaryDirectory() as td:
890 with TemporaryDirectory() as td:
894 fname = os.path.join(td, '"file1"')
891 fname = os.path.join(td, '"file1"')
895 ip.run_cell_magic("writefile", fname, u'\n'.join([
892 ip.run_cell_magic("writefile", fname, u'\n'.join([
896 'line1',
893 'line1',
897 'line2',
894 'line2',
898 ]))
895 ]))
899 s = Path(fname).read_text()
896 s = Path(fname).read_text()
900 assert "line1\n" in s
897 assert "line1\n" in s
901 assert "line2" in s
898 assert "line2" in s
902
899
903
900
904 def test_file_var_expand():
901 def test_file_var_expand():
905 """%%writefile $filename"""
902 """%%writefile $filename"""
906 ip = get_ipython()
903 ip = get_ipython()
907 with TemporaryDirectory() as td:
904 with TemporaryDirectory() as td:
908 fname = os.path.join(td, 'file1')
905 fname = os.path.join(td, 'file1')
909 ip.user_ns['filename'] = fname
906 ip.user_ns['filename'] = fname
910 ip.run_cell_magic("writefile", '$filename', u'\n'.join([
907 ip.run_cell_magic("writefile", '$filename', u'\n'.join([
911 'line1',
908 'line1',
912 'line2',
909 'line2',
913 ]))
910 ]))
914 s = Path(fname).read_text()
911 s = Path(fname).read_text()
915 assert "line1\n" in s
912 assert "line1\n" in s
916 assert "line2" in s
913 assert "line2" in s
917
914
918
915
919 def test_file_unicode():
916 def test_file_unicode():
920 """%%writefile with unicode cell"""
917 """%%writefile with unicode cell"""
921 ip = get_ipython()
918 ip = get_ipython()
922 with TemporaryDirectory() as td:
919 with TemporaryDirectory() as td:
923 fname = os.path.join(td, 'file1')
920 fname = os.path.join(td, 'file1')
924 ip.run_cell_magic("writefile", fname, u'\n'.join([
921 ip.run_cell_magic("writefile", fname, u'\n'.join([
925 u'liné1',
922 u'liné1',
926 u'liné2',
923 u'liné2',
927 ]))
924 ]))
928 with io.open(fname, encoding='utf-8') as f:
925 with io.open(fname, encoding='utf-8') as f:
929 s = f.read()
926 s = f.read()
930 assert "liné1\n" in s
927 assert "liné1\n" in s
931 assert "liné2" in s
928 assert "liné2" in s
932
929
933
930
934 def test_file_amend():
931 def test_file_amend():
935 """%%writefile -a amends files"""
932 """%%writefile -a amends files"""
936 ip = get_ipython()
933 ip = get_ipython()
937 with TemporaryDirectory() as td:
934 with TemporaryDirectory() as td:
938 fname = os.path.join(td, 'file2')
935 fname = os.path.join(td, 'file2')
939 ip.run_cell_magic("writefile", fname, u'\n'.join([
936 ip.run_cell_magic("writefile", fname, u'\n'.join([
940 'line1',
937 'line1',
941 'line2',
938 'line2',
942 ]))
939 ]))
943 ip.run_cell_magic("writefile", "-a %s" % fname, u'\n'.join([
940 ip.run_cell_magic("writefile", "-a %s" % fname, u'\n'.join([
944 'line3',
941 'line3',
945 'line4',
942 'line4',
946 ]))
943 ]))
947 s = Path(fname).read_text()
944 s = Path(fname).read_text()
948 assert "line1\n" in s
945 assert "line1\n" in s
949 assert "line3\n" in s
946 assert "line3\n" in s
950
947
951
948
952 def test_file_spaces():
949 def test_file_spaces():
953 """%%file with spaces in filename"""
950 """%%file with spaces in filename"""
954 ip = get_ipython()
951 ip = get_ipython()
955 with TemporaryWorkingDirectory() as td:
952 with TemporaryWorkingDirectory() as td:
956 fname = "file name"
953 fname = "file name"
957 ip.run_cell_magic("file", '"%s"'%fname, u'\n'.join([
954 ip.run_cell_magic("file", '"%s"'%fname, u'\n'.join([
958 'line1',
955 'line1',
959 'line2',
956 'line2',
960 ]))
957 ]))
961 s = Path(fname).read_text()
958 s = Path(fname).read_text()
962 assert "line1\n" in s
959 assert "line1\n" in s
963 assert "line2" in s
960 assert "line2" in s
964
961
965
962
966 def test_script_config():
963 def test_script_config():
967 ip = get_ipython()
964 ip = get_ipython()
968 ip.config.ScriptMagics.script_magics = ['whoda']
965 ip.config.ScriptMagics.script_magics = ['whoda']
969 sm = script.ScriptMagics(shell=ip)
966 sm = script.ScriptMagics(shell=ip)
970 assert "whoda" in sm.magics["cell"]
967 assert "whoda" in sm.magics["cell"]
971
968
972
969
973 @dec.skip_iptest_but_not_pytest
970 @dec.skip_iptest_but_not_pytest
974 @dec.skip_win32
971 @dec.skip_win32
975 @pytest.mark.skipif(
972 @pytest.mark.skipif(
976 sys.platform == "win32", reason="This test does not run under Windows"
973 sys.platform == "win32", reason="This test does not run under Windows"
977 )
974 )
978 def test_script_out():
975 def test_script_out():
979 assert asyncio.get_event_loop().is_running() is False
976 assert asyncio.get_event_loop().is_running() is False
980
977
981 ip = get_ipython()
978 ip = get_ipython()
982 ip.run_cell_magic("script", "--out output sh", "echo 'hi'")
979 ip.run_cell_magic("script", "--out output sh", "echo 'hi'")
983 assert asyncio.get_event_loop().is_running() is False
980 assert asyncio.get_event_loop().is_running() is False
984 assert ip.user_ns["output"] == "hi\n"
981 assert ip.user_ns["output"] == "hi\n"
985
982
986
983
987 @dec.skip_iptest_but_not_pytest
984 @dec.skip_iptest_but_not_pytest
988 @dec.skip_win32
985 @dec.skip_win32
989 @pytest.mark.skipif(
986 @pytest.mark.skipif(
990 sys.platform == "win32", reason="This test does not run under Windows"
987 sys.platform == "win32", reason="This test does not run under Windows"
991 )
988 )
992 def test_script_err():
989 def test_script_err():
993 ip = get_ipython()
990 ip = get_ipython()
994 assert asyncio.get_event_loop().is_running() is False
991 assert asyncio.get_event_loop().is_running() is False
995 ip.run_cell_magic("script", "--err error sh", "echo 'hello' >&2")
992 ip.run_cell_magic("script", "--err error sh", "echo 'hello' >&2")
996 assert asyncio.get_event_loop().is_running() is False
993 assert asyncio.get_event_loop().is_running() is False
997 assert ip.user_ns["error"] == "hello\n"
994 assert ip.user_ns["error"] == "hello\n"
998
995
999
996
1000 @dec.skip_iptest_but_not_pytest
997 @dec.skip_iptest_but_not_pytest
1001 @dec.skip_win32
998 @dec.skip_win32
1002 @pytest.mark.skipif(
999 @pytest.mark.skipif(
1003 sys.platform == "win32", reason="This test does not run under Windows"
1000 sys.platform == "win32", reason="This test does not run under Windows"
1004 )
1001 )
1005 def test_script_out_err():
1002 def test_script_out_err():
1006
1003
1007 ip = get_ipython()
1004 ip = get_ipython()
1008 ip.run_cell_magic(
1005 ip.run_cell_magic(
1009 "script", "--out output --err error sh", "echo 'hi'\necho 'hello' >&2"
1006 "script", "--out output --err error sh", "echo 'hi'\necho 'hello' >&2"
1010 )
1007 )
1011 assert ip.user_ns["output"] == "hi\n"
1008 assert ip.user_ns["output"] == "hi\n"
1012 assert ip.user_ns["error"] == "hello\n"
1009 assert ip.user_ns["error"] == "hello\n"
1013
1010
1014
1011
1015 @dec.skip_iptest_but_not_pytest
1012 @dec.skip_iptest_but_not_pytest
1016 @dec.skip_win32
1013 @dec.skip_win32
1017 @pytest.mark.skipif(
1014 @pytest.mark.skipif(
1018 sys.platform == "win32", reason="This test does not run under Windows"
1015 sys.platform == "win32", reason="This test does not run under Windows"
1019 )
1016 )
1020 async def test_script_bg_out():
1017 async def test_script_bg_out():
1021 ip = get_ipython()
1018 ip = get_ipython()
1022 ip.run_cell_magic("script", "--bg --out output sh", "echo 'hi'")
1019 ip.run_cell_magic("script", "--bg --out output sh", "echo 'hi'")
1023 assert (await ip.user_ns["output"].read()) == b"hi\n"
1020 assert (await ip.user_ns["output"].read()) == b"hi\n"
1024 ip.user_ns["output"].close()
1021 ip.user_ns["output"].close()
1025 asyncio.get_event_loop().stop()
1022 asyncio.get_event_loop().stop()
1026
1023
1027
1024
1028 @dec.skip_iptest_but_not_pytest
1025 @dec.skip_iptest_but_not_pytest
1029 @dec.skip_win32
1026 @dec.skip_win32
1030 @pytest.mark.skipif(
1027 @pytest.mark.skipif(
1031 sys.platform == "win32", reason="This test does not run under Windows"
1028 sys.platform == "win32", reason="This test does not run under Windows"
1032 )
1029 )
1033 async def test_script_bg_err():
1030 async def test_script_bg_err():
1034 ip = get_ipython()
1031 ip = get_ipython()
1035 ip.run_cell_magic("script", "--bg --err error sh", "echo 'hello' >&2")
1032 ip.run_cell_magic("script", "--bg --err error sh", "echo 'hello' >&2")
1036 assert (await ip.user_ns["error"].read()) == b"hello\n"
1033 assert (await ip.user_ns["error"].read()) == b"hello\n"
1037 ip.user_ns["error"].close()
1034 ip.user_ns["error"].close()
1038
1035
1039
1036
1040 @dec.skip_iptest_but_not_pytest
1037 @dec.skip_iptest_but_not_pytest
1041 @dec.skip_win32
1038 @dec.skip_win32
1042 @pytest.mark.skipif(
1039 @pytest.mark.skipif(
1043 sys.platform == "win32", reason="This test does not run under Windows"
1040 sys.platform == "win32", reason="This test does not run under Windows"
1044 )
1041 )
1045 async def test_script_bg_out_err():
1042 async def test_script_bg_out_err():
1046 ip = get_ipython()
1043 ip = get_ipython()
1047 ip.run_cell_magic(
1044 ip.run_cell_magic(
1048 "script", "--bg --out output --err error sh", "echo 'hi'\necho 'hello' >&2"
1045 "script", "--bg --out output --err error sh", "echo 'hi'\necho 'hello' >&2"
1049 )
1046 )
1050 assert (await ip.user_ns["output"].read()) == b"hi\n"
1047 assert (await ip.user_ns["output"].read()) == b"hi\n"
1051 assert (await ip.user_ns["error"].read()) == b"hello\n"
1048 assert (await ip.user_ns["error"].read()) == b"hello\n"
1052 ip.user_ns["output"].close()
1049 ip.user_ns["output"].close()
1053 ip.user_ns["error"].close()
1050 ip.user_ns["error"].close()
1054
1051
1055
1052
1056 def test_script_defaults():
1053 def test_script_defaults():
1057 ip = get_ipython()
1054 ip = get_ipython()
1058 for cmd in ['sh', 'bash', 'perl', 'ruby']:
1055 for cmd in ['sh', 'bash', 'perl', 'ruby']:
1059 try:
1056 try:
1060 find_cmd(cmd)
1057 find_cmd(cmd)
1061 except Exception:
1058 except Exception:
1062 pass
1059 pass
1063 else:
1060 else:
1064 assert cmd in ip.magics_manager.magics["cell"]
1061 assert cmd in ip.magics_manager.magics["cell"]
1065
1062
1066
1063
1067 @magics_class
1064 @magics_class
1068 class FooFoo(Magics):
1065 class FooFoo(Magics):
1069 """class with both %foo and %%foo magics"""
1066 """class with both %foo and %%foo magics"""
1070 @line_magic('foo')
1067 @line_magic('foo')
1071 def line_foo(self, line):
1068 def line_foo(self, line):
1072 "I am line foo"
1069 "I am line foo"
1073 pass
1070 pass
1074
1071
1075 @cell_magic("foo")
1072 @cell_magic("foo")
1076 def cell_foo(self, line, cell):
1073 def cell_foo(self, line, cell):
1077 "I am cell foo, not line foo"
1074 "I am cell foo, not line foo"
1078 pass
1075 pass
1079
1076
1080 def test_line_cell_info():
1077 def test_line_cell_info():
1081 """%%foo and %foo magics are distinguishable to inspect"""
1078 """%%foo and %foo magics are distinguishable to inspect"""
1082 ip = get_ipython()
1079 ip = get_ipython()
1083 ip.magics_manager.register(FooFoo)
1080 ip.magics_manager.register(FooFoo)
1084 oinfo = ip.object_inspect("foo")
1081 oinfo = ip.object_inspect("foo")
1085 assert oinfo["found"] is True
1082 assert oinfo["found"] is True
1086 assert oinfo["ismagic"] is True
1083 assert oinfo["ismagic"] is True
1087
1084
1088 oinfo = ip.object_inspect("%%foo")
1085 oinfo = ip.object_inspect("%%foo")
1089 assert oinfo["found"] is True
1086 assert oinfo["found"] is True
1090 assert oinfo["ismagic"] is True
1087 assert oinfo["ismagic"] is True
1091 assert oinfo["docstring"] == FooFoo.cell_foo.__doc__
1088 assert oinfo["docstring"] == FooFoo.cell_foo.__doc__
1092
1089
1093 oinfo = ip.object_inspect("%foo")
1090 oinfo = ip.object_inspect("%foo")
1094 assert oinfo["found"] is True
1091 assert oinfo["found"] is True
1095 assert oinfo["ismagic"] is True
1092 assert oinfo["ismagic"] is True
1096 assert oinfo["docstring"] == FooFoo.line_foo.__doc__
1093 assert oinfo["docstring"] == FooFoo.line_foo.__doc__
1097
1094
1098
1095
1099 def test_multiple_magics():
1096 def test_multiple_magics():
1100 ip = get_ipython()
1097 ip = get_ipython()
1101 foo1 = FooFoo(ip)
1098 foo1 = FooFoo(ip)
1102 foo2 = FooFoo(ip)
1099 foo2 = FooFoo(ip)
1103 mm = ip.magics_manager
1100 mm = ip.magics_manager
1104 mm.register(foo1)
1101 mm.register(foo1)
1105 assert mm.magics["line"]["foo"].__self__ is foo1
1102 assert mm.magics["line"]["foo"].__self__ is foo1
1106 mm.register(foo2)
1103 mm.register(foo2)
1107 assert mm.magics["line"]["foo"].__self__ is foo2
1104 assert mm.magics["line"]["foo"].__self__ is foo2
1108
1105
1109
1106
1110 def test_alias_magic():
1107 def test_alias_magic():
1111 """Test %alias_magic."""
1108 """Test %alias_magic."""
1112 ip = get_ipython()
1109 ip = get_ipython()
1113 mm = ip.magics_manager
1110 mm = ip.magics_manager
1114
1111
1115 # Basic operation: both cell and line magics are created, if possible.
1112 # Basic operation: both cell and line magics are created, if possible.
1116 ip.run_line_magic("alias_magic", "timeit_alias timeit")
1113 ip.run_line_magic("alias_magic", "timeit_alias timeit")
1117 assert "timeit_alias" in mm.magics["line"]
1114 assert "timeit_alias" in mm.magics["line"]
1118 assert "timeit_alias" in mm.magics["cell"]
1115 assert "timeit_alias" in mm.magics["cell"]
1119
1116
1120 # --cell is specified, line magic not created.
1117 # --cell is specified, line magic not created.
1121 ip.run_line_magic("alias_magic", "--cell timeit_cell_alias timeit")
1118 ip.run_line_magic("alias_magic", "--cell timeit_cell_alias timeit")
1122 assert "timeit_cell_alias" not in mm.magics["line"]
1119 assert "timeit_cell_alias" not in mm.magics["line"]
1123 assert "timeit_cell_alias" in mm.magics["cell"]
1120 assert "timeit_cell_alias" in mm.magics["cell"]
1124
1121
1125 # Test that line alias is created successfully.
1122 # Test that line alias is created successfully.
1126 ip.run_line_magic("alias_magic", "--line env_alias env")
1123 ip.run_line_magic("alias_magic", "--line env_alias env")
1127 assert ip.run_line_magic("env", "") == ip.run_line_magic("env_alias", "")
1124 assert ip.run_line_magic("env", "") == ip.run_line_magic("env_alias", "")
1128
1125
1129 # Test that line alias with parameters passed in is created successfully.
1126 # Test that line alias with parameters passed in is created successfully.
1130 ip.run_line_magic(
1127 ip.run_line_magic(
1131 "alias_magic", "--line history_alias history --params " + shlex.quote("3")
1128 "alias_magic", "--line history_alias history --params " + shlex.quote("3")
1132 )
1129 )
1133 assert "history_alias" in mm.magics["line"]
1130 assert "history_alias" in mm.magics["line"]
1134
1131
1135
1132
1136 def test_save():
1133 def test_save():
1137 """Test %save."""
1134 """Test %save."""
1138 ip = get_ipython()
1135 ip = get_ipython()
1139 ip.history_manager.reset() # Clear any existing history.
1136 ip.history_manager.reset() # Clear any existing history.
1140 cmds = ["a=1", "def b():\n return a**2", "print(a, b())"]
1137 cmds = ["a=1", "def b():\n return a**2", "print(a, b())"]
1141 for i, cmd in enumerate(cmds, start=1):
1138 for i, cmd in enumerate(cmds, start=1):
1142 ip.history_manager.store_inputs(i, cmd)
1139 ip.history_manager.store_inputs(i, cmd)
1143 with TemporaryDirectory() as tmpdir:
1140 with TemporaryDirectory() as tmpdir:
1144 file = os.path.join(tmpdir, "testsave.py")
1141 file = os.path.join(tmpdir, "testsave.py")
1145 ip.run_line_magic("save", "%s 1-10" % file)
1142 ip.run_line_magic("save", "%s 1-10" % file)
1146 content = Path(file).read_text()
1143 content = Path(file).read_text()
1147 assert content.count(cmds[0]) == 1
1144 assert content.count(cmds[0]) == 1
1148 assert "coding: utf-8" in content
1145 assert "coding: utf-8" in content
1149 ip.run_line_magic("save", "-a %s 1-10" % file)
1146 ip.run_line_magic("save", "-a %s 1-10" % file)
1150 content = Path(file).read_text()
1147 content = Path(file).read_text()
1151 assert content.count(cmds[0]) == 2
1148 assert content.count(cmds[0]) == 2
1152 assert "coding: utf-8" in content
1149 assert "coding: utf-8" in content
1153
1150
1154
1151
1155 def test_save_with_no_args():
1152 def test_save_with_no_args():
1156 ip = get_ipython()
1153 ip = get_ipython()
1157 ip.history_manager.reset() # Clear any existing history.
1154 ip.history_manager.reset() # Clear any existing history.
1158 cmds = ["a=1", "def b():\n return a**2", "print(a, b())", "%save"]
1155 cmds = ["a=1", "def b():\n return a**2", "print(a, b())", "%save"]
1159 for i, cmd in enumerate(cmds, start=1):
1156 for i, cmd in enumerate(cmds, start=1):
1160 ip.history_manager.store_inputs(i, cmd)
1157 ip.history_manager.store_inputs(i, cmd)
1161
1158
1162 with TemporaryDirectory() as tmpdir:
1159 with TemporaryDirectory() as tmpdir:
1163 path = os.path.join(tmpdir, "testsave.py")
1160 path = os.path.join(tmpdir, "testsave.py")
1164 ip.run_line_magic("save", path)
1161 ip.run_line_magic("save", path)
1165 content = Path(path).read_text()
1162 content = Path(path).read_text()
1166 expected_content = dedent(
1163 expected_content = dedent(
1167 """\
1164 """\
1168 # coding: utf-8
1165 # coding: utf-8
1169 a=1
1166 a=1
1170 def b():
1167 def b():
1171 return a**2
1168 return a**2
1172 print(a, b())
1169 print(a, b())
1173 """
1170 """
1174 )
1171 )
1175 assert content == expected_content
1172 assert content == expected_content
1176
1173
1177
1174
1178 def test_store():
1175 def test_store():
1179 """Test %store."""
1176 """Test %store."""
1180 ip = get_ipython()
1177 ip = get_ipython()
1181 ip.run_line_magic('load_ext', 'storemagic')
1178 ip.run_line_magic('load_ext', 'storemagic')
1182
1179
1183 # make sure the storage is empty
1180 # make sure the storage is empty
1184 ip.run_line_magic("store", "-z")
1181 ip.run_line_magic("store", "-z")
1185 ip.user_ns["var"] = 42
1182 ip.user_ns["var"] = 42
1186 ip.run_line_magic("store", "var")
1183 ip.run_line_magic("store", "var")
1187 ip.user_ns["var"] = 39
1184 ip.user_ns["var"] = 39
1188 ip.run_line_magic("store", "-r")
1185 ip.run_line_magic("store", "-r")
1189 assert ip.user_ns["var"] == 42
1186 assert ip.user_ns["var"] == 42
1190
1187
1191 ip.run_line_magic("store", "-d var")
1188 ip.run_line_magic("store", "-d var")
1192 ip.user_ns["var"] = 39
1189 ip.user_ns["var"] = 39
1193 ip.run_line_magic("store", "-r")
1190 ip.run_line_magic("store", "-r")
1194 assert ip.user_ns["var"] == 39
1191 assert ip.user_ns["var"] == 39
1195
1192
1196
1193
1197 def _run_edit_test(arg_s, exp_filename=None,
1194 def _run_edit_test(arg_s, exp_filename=None,
1198 exp_lineno=-1,
1195 exp_lineno=-1,
1199 exp_contents=None,
1196 exp_contents=None,
1200 exp_is_temp=None):
1197 exp_is_temp=None):
1201 ip = get_ipython()
1198 ip = get_ipython()
1202 M = code.CodeMagics(ip)
1199 M = code.CodeMagics(ip)
1203 last_call = ['','']
1200 last_call = ['','']
1204 opts,args = M.parse_options(arg_s,'prxn:')
1201 opts,args = M.parse_options(arg_s,'prxn:')
1205 filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call)
1202 filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call)
1206
1203
1207 if exp_filename is not None:
1204 if exp_filename is not None:
1208 assert exp_filename == filename
1205 assert exp_filename == filename
1209 if exp_contents is not None:
1206 if exp_contents is not None:
1210 with io.open(filename, 'r', encoding='utf-8') as f:
1207 with io.open(filename, 'r', encoding='utf-8') as f:
1211 contents = f.read()
1208 contents = f.read()
1212 assert exp_contents == contents
1209 assert exp_contents == contents
1213 if exp_lineno != -1:
1210 if exp_lineno != -1:
1214 assert exp_lineno == lineno
1211 assert exp_lineno == lineno
1215 if exp_is_temp is not None:
1212 if exp_is_temp is not None:
1216 assert exp_is_temp == is_temp
1213 assert exp_is_temp == is_temp
1217
1214
1218
1215
1219 def test_edit_interactive():
1216 def test_edit_interactive():
1220 """%edit on interactively defined objects"""
1217 """%edit on interactively defined objects"""
1221 ip = get_ipython()
1218 ip = get_ipython()
1222 n = ip.execution_count
1219 n = ip.execution_count
1223 ip.run_cell("def foo(): return 1", store_history=True)
1220 ip.run_cell("def foo(): return 1", store_history=True)
1224
1221
1225 try:
1222 try:
1226 _run_edit_test("foo")
1223 _run_edit_test("foo")
1227 except code.InteractivelyDefined as e:
1224 except code.InteractivelyDefined as e:
1228 assert e.index == n
1225 assert e.index == n
1229 else:
1226 else:
1230 raise AssertionError("Should have raised InteractivelyDefined")
1227 raise AssertionError("Should have raised InteractivelyDefined")
1231
1228
1232
1229
1233 def test_edit_cell():
1230 def test_edit_cell():
1234 """%edit [cell id]"""
1231 """%edit [cell id]"""
1235 ip = get_ipython()
1232 ip = get_ipython()
1236
1233
1237 ip.run_cell("def foo(): return 1", store_history=True)
1234 ip.run_cell("def foo(): return 1", store_history=True)
1238
1235
1239 # test
1236 # test
1240 _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True)
1237 _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True)
1241
1238
1242 def test_edit_fname():
1239 def test_edit_fname():
1243 """%edit file"""
1240 """%edit file"""
1244 # test
1241 # test
1245 _run_edit_test("test file.py", exp_filename="test file.py")
1242 _run_edit_test("test file.py", exp_filename="test file.py")
1246
1243
1247 def test_bookmark():
1244 def test_bookmark():
1248 ip = get_ipython()
1245 ip = get_ipython()
1249 ip.run_line_magic('bookmark', 'bmname')
1246 ip.run_line_magic('bookmark', 'bmname')
1250 with tt.AssertPrints('bmname'):
1247 with tt.AssertPrints('bmname'):
1251 ip.run_line_magic('bookmark', '-l')
1248 ip.run_line_magic('bookmark', '-l')
1252 ip.run_line_magic('bookmark', '-d bmname')
1249 ip.run_line_magic('bookmark', '-d bmname')
1253
1250
1254 def test_ls_magic():
1251 def test_ls_magic():
1255 ip = get_ipython()
1252 ip = get_ipython()
1256 json_formatter = ip.display_formatter.formatters['application/json']
1253 json_formatter = ip.display_formatter.formatters['application/json']
1257 json_formatter.enabled = True
1254 json_formatter.enabled = True
1258 lsmagic = ip.magic('lsmagic')
1255 lsmagic = ip.magic('lsmagic')
1259 with warnings.catch_warnings(record=True) as w:
1256 with warnings.catch_warnings(record=True) as w:
1260 j = json_formatter(lsmagic)
1257 j = json_formatter(lsmagic)
1261 assert sorted(j) == ["cell", "line"]
1258 assert sorted(j) == ["cell", "line"]
1262 assert w == [] # no warnings
1259 assert w == [] # no warnings
1263
1260
1264
1261
1265 def test_strip_initial_indent():
1262 def test_strip_initial_indent():
1266 def sii(s):
1263 def sii(s):
1267 lines = s.splitlines()
1264 lines = s.splitlines()
1268 return '\n'.join(code.strip_initial_indent(lines))
1265 return '\n'.join(code.strip_initial_indent(lines))
1269
1266
1270 assert sii(" a = 1\nb = 2") == "a = 1\nb = 2"
1267 assert sii(" a = 1\nb = 2") == "a = 1\nb = 2"
1271 assert sii(" a\n b\nc") == "a\n b\nc"
1268 assert sii(" a\n b\nc") == "a\n b\nc"
1272 assert sii("a\n b") == "a\n b"
1269 assert sii("a\n b") == "a\n b"
1273
1270
1274 def test_logging_magic_quiet_from_arg():
1271 def test_logging_magic_quiet_from_arg():
1275 _ip.config.LoggingMagics.quiet = False
1272 _ip.config.LoggingMagics.quiet = False
1276 lm = logging.LoggingMagics(shell=_ip)
1273 lm = logging.LoggingMagics(shell=_ip)
1277 with TemporaryDirectory() as td:
1274 with TemporaryDirectory() as td:
1278 try:
1275 try:
1279 with tt.AssertNotPrints(re.compile("Activating.*")):
1276 with tt.AssertNotPrints(re.compile("Activating.*")):
1280 lm.logstart('-q {}'.format(
1277 lm.logstart('-q {}'.format(
1281 os.path.join(td, "quiet_from_arg.log")))
1278 os.path.join(td, "quiet_from_arg.log")))
1282 finally:
1279 finally:
1283 _ip.logger.logstop()
1280 _ip.logger.logstop()
1284
1281
1285 def test_logging_magic_quiet_from_config():
1282 def test_logging_magic_quiet_from_config():
1286 _ip.config.LoggingMagics.quiet = True
1283 _ip.config.LoggingMagics.quiet = True
1287 lm = logging.LoggingMagics(shell=_ip)
1284 lm = logging.LoggingMagics(shell=_ip)
1288 with TemporaryDirectory() as td:
1285 with TemporaryDirectory() as td:
1289 try:
1286 try:
1290 with tt.AssertNotPrints(re.compile("Activating.*")):
1287 with tt.AssertNotPrints(re.compile("Activating.*")):
1291 lm.logstart(os.path.join(td, "quiet_from_config.log"))
1288 lm.logstart(os.path.join(td, "quiet_from_config.log"))
1292 finally:
1289 finally:
1293 _ip.logger.logstop()
1290 _ip.logger.logstop()
1294
1291
1295
1292
1296 def test_logging_magic_not_quiet():
1293 def test_logging_magic_not_quiet():
1297 _ip.config.LoggingMagics.quiet = False
1294 _ip.config.LoggingMagics.quiet = False
1298 lm = logging.LoggingMagics(shell=_ip)
1295 lm = logging.LoggingMagics(shell=_ip)
1299 with TemporaryDirectory() as td:
1296 with TemporaryDirectory() as td:
1300 try:
1297 try:
1301 with tt.AssertPrints(re.compile("Activating.*")):
1298 with tt.AssertPrints(re.compile("Activating.*")):
1302 lm.logstart(os.path.join(td, "not_quiet.log"))
1299 lm.logstart(os.path.join(td, "not_quiet.log"))
1303 finally:
1300 finally:
1304 _ip.logger.logstop()
1301 _ip.logger.logstop()
1305
1302
1306
1303
1307 def test_time_no_var_expand():
1304 def test_time_no_var_expand():
1308 _ip.user_ns['a'] = 5
1305 _ip.user_ns['a'] = 5
1309 _ip.user_ns['b'] = []
1306 _ip.user_ns['b'] = []
1310 _ip.magic('time b.append("{a}")')
1307 _ip.magic('time b.append("{a}")')
1311 assert _ip.user_ns['b'] == ['{a}']
1308 assert _ip.user_ns['b'] == ['{a}']
1312
1309
1313
1310
1314 # this is slow, put at the end for local testing.
1311 # this is slow, put at the end for local testing.
1315 def test_timeit_arguments():
1312 def test_timeit_arguments():
1316 "Test valid timeit arguments, should not cause SyntaxError (GH #1269)"
1313 "Test valid timeit arguments, should not cause SyntaxError (GH #1269)"
1317 if sys.version_info < (3,7):
1314 if sys.version_info < (3,7):
1318 _ip.magic("timeit -n1 -r1 ('#')")
1315 _ip.magic("timeit -n1 -r1 ('#')")
1319 else:
1316 else:
1320 # 3.7 optimize no-op statement like above out, and complain there is
1317 # 3.7 optimize no-op statement like above out, and complain there is
1321 # nothing in the for loop.
1318 # nothing in the for loop.
1322 _ip.magic("timeit -n1 -r1 a=('#')")
1319 _ip.magic("timeit -n1 -r1 a=('#')")
1323
1320
1324
1321
1325 TEST_MODULE = """
1322 TEST_MODULE = """
1326 print('Loaded my_tmp')
1323 print('Loaded my_tmp')
1327 if __name__ == "__main__":
1324 if __name__ == "__main__":
1328 print('I just ran a script')
1325 print('I just ran a script')
1329 """
1326 """
1330
1327
1331
1328
1332 def test_run_module_from_import_hook():
1329 def test_run_module_from_import_hook():
1333 "Test that a module can be loaded via an import hook"
1330 "Test that a module can be loaded via an import hook"
1334 with TemporaryDirectory() as tmpdir:
1331 with TemporaryDirectory() as tmpdir:
1335 fullpath = os.path.join(tmpdir, 'my_tmp.py')
1332 fullpath = os.path.join(tmpdir, 'my_tmp.py')
1336 Path(fullpath).write_text(TEST_MODULE)
1333 Path(fullpath).write_text(TEST_MODULE)
1337
1334
1338 class MyTempImporter(object):
1335 class MyTempImporter(object):
1339 def __init__(self):
1336 def __init__(self):
1340 pass
1337 pass
1341
1338
1342 def find_module(self, fullname, path=None):
1339 def find_module(self, fullname, path=None):
1343 if 'my_tmp' in fullname:
1340 if 'my_tmp' in fullname:
1344 return self
1341 return self
1345 return None
1342 return None
1346
1343
1347 def load_module(self, name):
1344 def load_module(self, name):
1348 import imp
1345 import imp
1349 return imp.load_source('my_tmp', fullpath)
1346 return imp.load_source('my_tmp', fullpath)
1350
1347
1351 def get_code(self, fullname):
1348 def get_code(self, fullname):
1352 return compile(Path(fullpath).read_text(), "foo", "exec")
1349 return compile(Path(fullpath).read_text(), "foo", "exec")
1353
1350
1354 def is_package(self, __):
1351 def is_package(self, __):
1355 return False
1352 return False
1356
1353
1357 sys.meta_path.insert(0, MyTempImporter())
1354 sys.meta_path.insert(0, MyTempImporter())
1358
1355
1359 with capture_output() as captured:
1356 with capture_output() as captured:
1360 _ip.magic("run -m my_tmp")
1357 _ip.magic("run -m my_tmp")
1361 _ip.run_cell("import my_tmp")
1358 _ip.run_cell("import my_tmp")
1362
1359
1363 output = "Loaded my_tmp\nI just ran a script\nLoaded my_tmp\n"
1360 output = "Loaded my_tmp\nI just ran a script\nLoaded my_tmp\n"
1364 assert output == captured.stdout
1361 assert output == captured.stdout
1365
1362
1366 sys.meta_path.pop(0)
1363 sys.meta_path.pop(0)
@@ -1,193 +1,190 b''
1 """Tests for various magic functions specific to the terminal frontend.
1 """Tests for various magic functions specific to the terminal frontend."""
2
3 Needs to be run by nose (to make ipython session available).
4 """
5
2
6 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
7 # Imports
4 # Imports
8 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
9
6
10 import sys
7 import sys
11 from io import StringIO
8 from io import StringIO
12 from unittest import TestCase
9 from unittest import TestCase
13
10
14 from IPython.testing import tools as tt
11 from IPython.testing import tools as tt
15
12
16 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
17 # Test functions begin
14 # Test functions begin
18 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
19
16
20 def check_cpaste(code, should_fail=False):
17 def check_cpaste(code, should_fail=False):
21 """Execute code via 'cpaste' and ensure it was executed, unless
18 """Execute code via 'cpaste' and ensure it was executed, unless
22 should_fail is set.
19 should_fail is set.
23 """
20 """
24 ip.user_ns['code_ran'] = False
21 ip.user_ns['code_ran'] = False
25
22
26 src = StringIO()
23 src = StringIO()
27 if not hasattr(src, 'encoding'):
24 if not hasattr(src, 'encoding'):
28 # IPython expects stdin to have an encoding attribute
25 # IPython expects stdin to have an encoding attribute
29 src.encoding = None
26 src.encoding = None
30 src.write(code)
27 src.write(code)
31 src.write('\n--\n')
28 src.write('\n--\n')
32 src.seek(0)
29 src.seek(0)
33
30
34 stdin_save = sys.stdin
31 stdin_save = sys.stdin
35 sys.stdin = src
32 sys.stdin = src
36
33
37 try:
34 try:
38 context = tt.AssertPrints if should_fail else tt.AssertNotPrints
35 context = tt.AssertPrints if should_fail else tt.AssertNotPrints
39 with context("Traceback (most recent call last)"):
36 with context("Traceback (most recent call last)"):
40 ip.magic('cpaste')
37 ip.magic('cpaste')
41
38
42 if not should_fail:
39 if not should_fail:
43 assert ip.user_ns['code_ran'], "%r failed" % code
40 assert ip.user_ns['code_ran'], "%r failed" % code
44 finally:
41 finally:
45 sys.stdin = stdin_save
42 sys.stdin = stdin_save
46
43
47 def test_cpaste():
44 def test_cpaste():
48 """Test cpaste magic"""
45 """Test cpaste magic"""
49
46
50 def runf():
47 def runf():
51 """Marker function: sets a flag when executed.
48 """Marker function: sets a flag when executed.
52 """
49 """
53 ip.user_ns['code_ran'] = True
50 ip.user_ns['code_ran'] = True
54 return 'runf' # return string so '+ runf()' doesn't result in success
51 return 'runf' # return string so '+ runf()' doesn't result in success
55
52
56 tests = {'pass': ["runf()",
53 tests = {'pass': ["runf()",
57 "In [1]: runf()",
54 "In [1]: runf()",
58 "In [1]: if 1:\n ...: runf()",
55 "In [1]: if 1:\n ...: runf()",
59 "> > > runf()",
56 "> > > runf()",
60 ">>> runf()",
57 ">>> runf()",
61 " >>> runf()",
58 " >>> runf()",
62 ],
59 ],
63
60
64 'fail': ["1 + runf()",
61 'fail': ["1 + runf()",
65 "++ runf()",
62 "++ runf()",
66 ]}
63 ]}
67
64
68 ip.user_ns['runf'] = runf
65 ip.user_ns['runf'] = runf
69
66
70 for code in tests['pass']:
67 for code in tests['pass']:
71 check_cpaste(code)
68 check_cpaste(code)
72
69
73 for code in tests['fail']:
70 for code in tests['fail']:
74 check_cpaste(code, should_fail=True)
71 check_cpaste(code, should_fail=True)
75
72
76
73
77 class PasteTestCase(TestCase):
74 class PasteTestCase(TestCase):
78 """Multiple tests for clipboard pasting"""
75 """Multiple tests for clipboard pasting"""
79
76
80 def paste(self, txt, flags='-q'):
77 def paste(self, txt, flags='-q'):
81 """Paste input text, by default in quiet mode"""
78 """Paste input text, by default in quiet mode"""
82 ip.hooks.clipboard_get = lambda : txt
79 ip.hooks.clipboard_get = lambda : txt
83 ip.magic('paste '+flags)
80 ip.magic('paste '+flags)
84
81
85 def setUp(self):
82 def setUp(self):
86 # Inject fake clipboard hook but save original so we can restore it later
83 # Inject fake clipboard hook but save original so we can restore it later
87 self.original_clip = ip.hooks.clipboard_get
84 self.original_clip = ip.hooks.clipboard_get
88
85
89 def tearDown(self):
86 def tearDown(self):
90 # Restore original hook
87 # Restore original hook
91 ip.hooks.clipboard_get = self.original_clip
88 ip.hooks.clipboard_get = self.original_clip
92
89
93 def test_paste(self):
90 def test_paste(self):
94 ip.user_ns.pop("x", None)
91 ip.user_ns.pop("x", None)
95 self.paste("x = 1")
92 self.paste("x = 1")
96 self.assertEqual(ip.user_ns["x"], 1)
93 self.assertEqual(ip.user_ns["x"], 1)
97 ip.user_ns.pop("x")
94 ip.user_ns.pop("x")
98
95
99 def test_paste_pyprompt(self):
96 def test_paste_pyprompt(self):
100 ip.user_ns.pop("x", None)
97 ip.user_ns.pop("x", None)
101 self.paste(">>> x=2")
98 self.paste(">>> x=2")
102 self.assertEqual(ip.user_ns["x"], 2)
99 self.assertEqual(ip.user_ns["x"], 2)
103 ip.user_ns.pop("x")
100 ip.user_ns.pop("x")
104
101
105 def test_paste_py_multi(self):
102 def test_paste_py_multi(self):
106 self.paste("""
103 self.paste("""
107 >>> x = [1,2,3]
104 >>> x = [1,2,3]
108 >>> y = []
105 >>> y = []
109 >>> for i in x:
106 >>> for i in x:
110 ... y.append(i**2)
107 ... y.append(i**2)
111 ...
108 ...
112 """
109 """
113 )
110 )
114 self.assertEqual(ip.user_ns["x"], [1, 2, 3])
111 self.assertEqual(ip.user_ns["x"], [1, 2, 3])
115 self.assertEqual(ip.user_ns["y"], [1, 4, 9])
112 self.assertEqual(ip.user_ns["y"], [1, 4, 9])
116
113
117 def test_paste_py_multi_r(self):
114 def test_paste_py_multi_r(self):
118 "Now, test that self.paste -r works"
115 "Now, test that self.paste -r works"
119 self.test_paste_py_multi()
116 self.test_paste_py_multi()
120 self.assertEqual(ip.user_ns.pop("x"), [1, 2, 3])
117 self.assertEqual(ip.user_ns.pop("x"), [1, 2, 3])
121 self.assertEqual(ip.user_ns.pop("y"), [1, 4, 9])
118 self.assertEqual(ip.user_ns.pop("y"), [1, 4, 9])
122 self.assertFalse("x" in ip.user_ns)
119 self.assertFalse("x" in ip.user_ns)
123 ip.magic("paste -r")
120 ip.magic("paste -r")
124 self.assertEqual(ip.user_ns["x"], [1, 2, 3])
121 self.assertEqual(ip.user_ns["x"], [1, 2, 3])
125 self.assertEqual(ip.user_ns["y"], [1, 4, 9])
122 self.assertEqual(ip.user_ns["y"], [1, 4, 9])
126
123
127 def test_paste_email(self):
124 def test_paste_email(self):
128 "Test pasting of email-quoted contents"
125 "Test pasting of email-quoted contents"
129 self.paste("""\
126 self.paste("""\
130 >> def foo(x):
127 >> def foo(x):
131 >> return x + 1
128 >> return x + 1
132 >> xx = foo(1.1)"""
129 >> xx = foo(1.1)"""
133 )
130 )
134 self.assertEqual(ip.user_ns["xx"], 2.1)
131 self.assertEqual(ip.user_ns["xx"], 2.1)
135
132
136 def test_paste_email2(self):
133 def test_paste_email2(self):
137 "Email again; some programs add a space also at each quoting level"
134 "Email again; some programs add a space also at each quoting level"
138 self.paste("""\
135 self.paste("""\
139 > > def foo(x):
136 > > def foo(x):
140 > > return x + 1
137 > > return x + 1
141 > > yy = foo(2.1) """
138 > > yy = foo(2.1) """
142 )
139 )
143 self.assertEqual(ip.user_ns["yy"], 3.1)
140 self.assertEqual(ip.user_ns["yy"], 3.1)
144
141
145 def test_paste_email_py(self):
142 def test_paste_email_py(self):
146 "Email quoting of interactive input"
143 "Email quoting of interactive input"
147 self.paste("""\
144 self.paste("""\
148 >> >>> def f(x):
145 >> >>> def f(x):
149 >> ... return x+1
146 >> ... return x+1
150 >> ...
147 >> ...
151 >> >>> zz = f(2.5) """
148 >> >>> zz = f(2.5) """
152 )
149 )
153 self.assertEqual(ip.user_ns["zz"], 3.5)
150 self.assertEqual(ip.user_ns["zz"], 3.5)
154
151
155 def test_paste_echo(self):
152 def test_paste_echo(self):
156 "Also test self.paste echoing, by temporarily faking the writer"
153 "Also test self.paste echoing, by temporarily faking the writer"
157 w = StringIO()
154 w = StringIO()
158 writer = ip.write
155 writer = ip.write
159 ip.write = w.write
156 ip.write = w.write
160 code = """
157 code = """
161 a = 100
158 a = 100
162 b = 200"""
159 b = 200"""
163 try:
160 try:
164 self.paste(code,'')
161 self.paste(code,'')
165 out = w.getvalue()
162 out = w.getvalue()
166 finally:
163 finally:
167 ip.write = writer
164 ip.write = writer
168 self.assertEqual(ip.user_ns["a"], 100)
165 self.assertEqual(ip.user_ns["a"], 100)
169 self.assertEqual(ip.user_ns["b"], 200)
166 self.assertEqual(ip.user_ns["b"], 200)
170 assert out == code + "\n## -- End pasted text --\n"
167 assert out == code + "\n## -- End pasted text --\n"
171
168
172 def test_paste_leading_commas(self):
169 def test_paste_leading_commas(self):
173 "Test multiline strings with leading commas"
170 "Test multiline strings with leading commas"
174 tm = ip.magics_manager.registry['TerminalMagics']
171 tm = ip.magics_manager.registry['TerminalMagics']
175 s = '''\
172 s = '''\
176 a = """
173 a = """
177 ,1,2,3
174 ,1,2,3
178 """'''
175 """'''
179 ip.user_ns.pop("foo", None)
176 ip.user_ns.pop("foo", None)
180 tm.store_or_execute(s, "foo")
177 tm.store_or_execute(s, "foo")
181 self.assertIn("foo", ip.user_ns)
178 self.assertIn("foo", ip.user_ns)
182
179
183 def test_paste_trailing_question(self):
180 def test_paste_trailing_question(self):
184 "Test pasting sources with trailing question marks"
181 "Test pasting sources with trailing question marks"
185 tm = ip.magics_manager.registry['TerminalMagics']
182 tm = ip.magics_manager.registry['TerminalMagics']
186 s = '''\
183 s = '''\
187 def funcfoo():
184 def funcfoo():
188 if True: #am i true?
185 if True: #am i true?
189 return 'fooresult'
186 return 'fooresult'
190 '''
187 '''
191 ip.user_ns.pop('funcfoo', None)
188 ip.user_ns.pop('funcfoo', None)
192 self.paste(s)
189 self.paste(s)
193 self.assertEqual(ip.user_ns["funcfoo"](), "fooresult")
190 self.assertEqual(ip.user_ns["funcfoo"](), "fooresult")
@@ -1,594 +1,599 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for code execution (%run and related), which is particularly tricky.
2 """Tests for code execution (%run and related), which is particularly tricky.
3
3
4 Because of how %run manages namespaces, and the fact that we are trying here to
4 Because of how %run manages namespaces, and the fact that we are trying here to
5 verify subtle object deletion and reference counting issues, the %run tests
5 verify subtle object deletion and reference counting issues, the %run tests
6 will be kept in this separate file. This makes it easier to aggregate in one
6 will be kept in this separate file. This makes it easier to aggregate in one
7 place the tricks needed to handle it; most other magics are much easier to test
7 place the tricks needed to handle it; most other magics are much easier to test
8 and we do so in a common test_magic file.
8 and we do so in a common test_magic file.
9
9
10 Note that any test using `run -i` should make sure to do a `reset` afterwards,
10 Note that any test using `run -i` should make sure to do a `reset` afterwards,
11 as otherwise it may influence later tests.
11 as otherwise it may influence later tests.
12 """
12 """
13
13
14 # Copyright (c) IPython Development Team.
14 # Copyright (c) IPython Development Team.
15 # Distributed under the terms of the Modified BSD License.
15 # Distributed under the terms of the Modified BSD License.
16
16
17
17
18
18
19 import functools
19 import functools
20 import os
20 import os
21 from os.path import join as pjoin
21 from os.path import join as pjoin
22 import random
22 import random
23 import string
23 import string
24 import sys
24 import sys
25 import textwrap
25 import textwrap
26 import unittest
26 import unittest
27 from unittest.mock import patch
27 from unittest.mock import patch
28
28
29 import pytest
29 import pytest
30
30
31 from IPython.testing import decorators as dec
31 from IPython.testing import decorators as dec
32 from IPython.testing import tools as tt
32 from IPython.testing import tools as tt
33 from IPython.utils.io import capture_output
33 from IPython.utils.io import capture_output
34 from IPython.utils.tempdir import TemporaryDirectory
34 from IPython.utils.tempdir import TemporaryDirectory
35 from IPython.core import debugger
35 from IPython.core import debugger
36
36
37 def doctest_refbug():
37 def doctest_refbug():
38 """Very nasty problem with references held by multiple runs of a script.
38 """Very nasty problem with references held by multiple runs of a script.
39 See: https://github.com/ipython/ipython/issues/141
39 See: https://github.com/ipython/ipython/issues/141
40
40
41 In [1]: _ip.clear_main_mod_cache()
41 In [1]: _ip.clear_main_mod_cache()
42 # random
42 # random
43
43
44 In [2]: %run refbug
44 In [2]: %run refbug
45
45
46 In [3]: call_f()
46 In [3]: call_f()
47 lowercased: hello
47 lowercased: hello
48
48
49 In [4]: %run refbug
49 In [4]: %run refbug
50
50
51 In [5]: call_f()
51 In [5]: call_f()
52 lowercased: hello
52 lowercased: hello
53 lowercased: hello
53 lowercased: hello
54 """
54 """
55
55
56
56
57 def doctest_run_builtins():
57 def doctest_run_builtins():
58 r"""Check that %run doesn't damage __builtins__.
58 r"""Check that %run doesn't damage __builtins__.
59
59
60 In [1]: import tempfile
60 In [1]: import tempfile
61
61
62 In [2]: bid1 = id(__builtins__)
62 In [2]: bid1 = id(__builtins__)
63
63
64 In [3]: fname = tempfile.mkstemp('.py')[1]
64 In [3]: fname = tempfile.mkstemp('.py')[1]
65
65
66 In [3]: f = open(fname,'w')
66 In [3]: f = open(fname,'w')
67
67
68 In [4]: dummy= f.write('pass\n')
68 In [4]: dummy= f.write('pass\n')
69
69
70 In [5]: f.flush()
70 In [5]: f.flush()
71
71
72 In [6]: t1 = type(__builtins__)
72 In [6]: t1 = type(__builtins__)
73
73
74 In [7]: %run $fname
74 In [7]: %run $fname
75
75
76 In [7]: f.close()
76 In [7]: f.close()
77
77
78 In [8]: bid2 = id(__builtins__)
78 In [8]: bid2 = id(__builtins__)
79
79
80 In [9]: t2 = type(__builtins__)
80 In [9]: t2 = type(__builtins__)
81
81
82 In [10]: t1 == t2
82 In [10]: t1 == t2
83 Out[10]: True
83 Out[10]: True
84
84
85 In [10]: bid1 == bid2
85 In [10]: bid1 == bid2
86 Out[10]: True
86 Out[10]: True
87
87
88 In [12]: try:
88 In [12]: try:
89 ....: os.unlink(fname)
89 ....: os.unlink(fname)
90 ....: except:
90 ....: except:
91 ....: pass
91 ....: pass
92 ....:
92 ....:
93 """
93 """
94
94
95
95
96 def doctest_run_option_parser():
96 def doctest_run_option_parser():
97 r"""Test option parser in %run.
97 r"""Test option parser in %run.
98
98
99 In [1]: %run print_argv.py
99 In [1]: %run print_argv.py
100 []
100 []
101
101
102 In [2]: %run print_argv.py print*.py
102 In [2]: %run print_argv.py print*.py
103 ['print_argv.py']
103 ['print_argv.py']
104
104
105 In [3]: %run -G print_argv.py print*.py
105 In [3]: %run -G print_argv.py print*.py
106 ['print*.py']
106 ['print*.py']
107
107
108 """
108 """
109
109
110
110
111 @dec.skip_win32
111 @dec.skip_win32
112 def doctest_run_option_parser_for_posix():
112 def doctest_run_option_parser_for_posix():
113 r"""Test option parser in %run (Linux/OSX specific).
113 r"""Test option parser in %run (Linux/OSX specific).
114
114
115 You need double quote to escape glob in POSIX systems:
115 You need double quote to escape glob in POSIX systems:
116
116
117 In [1]: %run print_argv.py print\\*.py
117 In [1]: %run print_argv.py print\\*.py
118 ['print*.py']
118 ['print*.py']
119
119
120 You can't use quote to escape glob in POSIX systems:
120 You can't use quote to escape glob in POSIX systems:
121
121
122 In [2]: %run print_argv.py 'print*.py'
122 In [2]: %run print_argv.py 'print*.py'
123 ['print_argv.py']
123 ['print_argv.py']
124
124
125 """
125 """
126
126
127
127
128 doctest_run_option_parser_for_posix.__skip_doctest__ = sys.platform == "win32"
129
130
128 @dec.skip_if_not_win32
131 @dec.skip_if_not_win32
129 def doctest_run_option_parser_for_windows():
132 def doctest_run_option_parser_for_windows():
130 r"""Test option parser in %run (Windows specific).
133 r"""Test option parser in %run (Windows specific).
131
134
132 In Windows, you can't escape ``*` `by backslash:
135 In Windows, you can't escape ``*` `by backslash:
133
136
134 In [1]: %run print_argv.py print\\*.py
137 In [1]: %run print_argv.py print\\*.py
135 ['print\\*.py']
138 ['print\\\\*.py']
136
139
137 You can use quote to escape glob:
140 You can use quote to escape glob:
138
141
139 In [2]: %run print_argv.py 'print*.py'
142 In [2]: %run print_argv.py 'print*.py'
140 ['print*.py']
143 ["'print*.py'"]
141
144
142 """
145 """
143
146
144
147
148 doctest_run_option_parser_for_windows.__skip_doctest__ = sys.platform != "win32"
149
150
145 def doctest_reset_del():
151 def doctest_reset_del():
146 """Test that resetting doesn't cause errors in __del__ methods.
152 """Test that resetting doesn't cause errors in __del__ methods.
147
153
148 In [2]: class A(object):
154 In [2]: class A(object):
149 ...: def __del__(self):
155 ...: def __del__(self):
150 ...: print(str("Hi"))
156 ...: print(str("Hi"))
151 ...:
157 ...:
152
158
153 In [3]: a = A()
159 In [3]: a = A()
154
160
155 In [4]: get_ipython().reset()
161 In [4]: get_ipython().reset()
156 Hi
162 Hi
157
163
158 In [5]: 1+1
164 In [5]: 1+1
159 Out[5]: 2
165 Out[5]: 2
160 """
166 """
161
167
162 # For some tests, it will be handy to organize them in a class with a common
168 # For some tests, it will be handy to organize them in a class with a common
163 # setup that makes a temp file
169 # setup that makes a temp file
164
170
165 class TestMagicRunPass(tt.TempFileMixin):
171 class TestMagicRunPass(tt.TempFileMixin):
166
172
167 def setUp(self):
173 def setUp(self):
168 content = "a = [1,2,3]\nb = 1"
174 content = "a = [1,2,3]\nb = 1"
169 self.mktmp(content)
175 self.mktmp(content)
170
176
171 def run_tmpfile(self):
177 def run_tmpfile(self):
172 _ip = get_ipython()
178 _ip = get_ipython()
173 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
179 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
174 # See below and ticket https://bugs.launchpad.net/bugs/366353
180 # See below and ticket https://bugs.launchpad.net/bugs/366353
175 _ip.magic('run %s' % self.fname)
181 _ip.magic('run %s' % self.fname)
176
182
177 def run_tmpfile_p(self):
183 def run_tmpfile_p(self):
178 _ip = get_ipython()
184 _ip = get_ipython()
179 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
185 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
180 # See below and ticket https://bugs.launchpad.net/bugs/366353
186 # See below and ticket https://bugs.launchpad.net/bugs/366353
181 _ip.magic('run -p %s' % self.fname)
187 _ip.magic('run -p %s' % self.fname)
182
188
183 def test_builtins_id(self):
189 def test_builtins_id(self):
184 """Check that %run doesn't damage __builtins__ """
190 """Check that %run doesn't damage __builtins__ """
185 _ip = get_ipython()
191 _ip = get_ipython()
186 # Test that the id of __builtins__ is not modified by %run
192 # Test that the id of __builtins__ is not modified by %run
187 bid1 = id(_ip.user_ns['__builtins__'])
193 bid1 = id(_ip.user_ns['__builtins__'])
188 self.run_tmpfile()
194 self.run_tmpfile()
189 bid2 = id(_ip.user_ns['__builtins__'])
195 bid2 = id(_ip.user_ns['__builtins__'])
190 assert bid1 == bid2
196 assert bid1 == bid2
191
197
192 def test_builtins_type(self):
198 def test_builtins_type(self):
193 """Check that the type of __builtins__ doesn't change with %run.
199 """Check that the type of __builtins__ doesn't change with %run.
194
200
195 However, the above could pass if __builtins__ was already modified to
201 However, the above could pass if __builtins__ was already modified to
196 be a dict (it should be a module) by a previous use of %run. So we
202 be a dict (it should be a module) by a previous use of %run. So we
197 also check explicitly that it really is a module:
203 also check explicitly that it really is a module:
198 """
204 """
199 _ip = get_ipython()
205 _ip = get_ipython()
200 self.run_tmpfile()
206 self.run_tmpfile()
201 assert type(_ip.user_ns["__builtins__"]) == type(sys)
207 assert type(_ip.user_ns["__builtins__"]) == type(sys)
202
208
203 def test_run_profile(self):
209 def test_run_profile(self):
204 """Test that the option -p, which invokes the profiler, do not
210 """Test that the option -p, which invokes the profiler, do not
205 crash by invoking execfile"""
211 crash by invoking execfile"""
206 self.run_tmpfile_p()
212 self.run_tmpfile_p()
207
213
208 def test_run_debug_twice(self):
214 def test_run_debug_twice(self):
209 # https://github.com/ipython/ipython/issues/10028
215 # https://github.com/ipython/ipython/issues/10028
210 _ip = get_ipython()
216 _ip = get_ipython()
211 with tt.fake_input(['c']):
217 with tt.fake_input(['c']):
212 _ip.magic('run -d %s' % self.fname)
218 _ip.magic('run -d %s' % self.fname)
213 with tt.fake_input(['c']):
219 with tt.fake_input(['c']):
214 _ip.magic('run -d %s' % self.fname)
220 _ip.magic('run -d %s' % self.fname)
215
221
216 def test_run_debug_twice_with_breakpoint(self):
222 def test_run_debug_twice_with_breakpoint(self):
217 """Make a valid python temp file."""
223 """Make a valid python temp file."""
218 _ip = get_ipython()
224 _ip = get_ipython()
219 with tt.fake_input(['b 2', 'c', 'c']):
225 with tt.fake_input(['b 2', 'c', 'c']):
220 _ip.magic('run -d %s' % self.fname)
226 _ip.magic('run -d %s' % self.fname)
221
227
222 with tt.fake_input(['c']):
228 with tt.fake_input(['c']):
223 with tt.AssertNotPrints('KeyError'):
229 with tt.AssertNotPrints('KeyError'):
224 _ip.magic('run -d %s' % self.fname)
230 _ip.magic('run -d %s' % self.fname)
225
231
226
232
227 class TestMagicRunSimple(tt.TempFileMixin):
233 class TestMagicRunSimple(tt.TempFileMixin):
228
234
229 def test_simpledef(self):
235 def test_simpledef(self):
230 """Test that simple class definitions work."""
236 """Test that simple class definitions work."""
231 src = ("class foo: pass\n"
237 src = ("class foo: pass\n"
232 "def f(): return foo()")
238 "def f(): return foo()")
233 self.mktmp(src)
239 self.mktmp(src)
234 _ip.magic("run %s" % self.fname)
240 _ip.magic("run %s" % self.fname)
235 _ip.run_cell("t = isinstance(f(), foo)")
241 _ip.run_cell("t = isinstance(f(), foo)")
236 assert _ip.user_ns["t"] is True
242 assert _ip.user_ns["t"] is True
237
243
238 def test_obj_del(self):
244 def test_obj_del(self):
239 """Test that object's __del__ methods are called on exit."""
245 """Test that object's __del__ methods are called on exit."""
240 src = ("class A(object):\n"
246 src = ("class A(object):\n"
241 " def __del__(self):\n"
247 " def __del__(self):\n"
242 " print('object A deleted')\n"
248 " print('object A deleted')\n"
243 "a = A()\n")
249 "a = A()\n")
244 self.mktmp(src)
250 self.mktmp(src)
245 err = None
251 err = None
246 tt.ipexec_validate(self.fname, 'object A deleted', err)
252 tt.ipexec_validate(self.fname, 'object A deleted', err)
247
253
248 def test_aggressive_namespace_cleanup(self):
254 def test_aggressive_namespace_cleanup(self):
249 """Test that namespace cleanup is not too aggressive GH-238
255 """Test that namespace cleanup is not too aggressive GH-238
250
256
251 Returning from another run magic deletes the namespace"""
257 Returning from another run magic deletes the namespace"""
252 # see ticket https://github.com/ipython/ipython/issues/238
258 # see ticket https://github.com/ipython/ipython/issues/238
253
259
254 with tt.TempFileMixin() as empty:
260 with tt.TempFileMixin() as empty:
255 empty.mktmp("")
261 empty.mktmp("")
256 # On Windows, the filename will have \users in it, so we need to use the
262 # On Windows, the filename will have \users in it, so we need to use the
257 # repr so that the \u becomes \\u.
263 # repr so that the \u becomes \\u.
258 src = (
264 src = (
259 "ip = get_ipython()\n"
265 "ip = get_ipython()\n"
260 "for i in range(5):\n"
266 "for i in range(5):\n"
261 " try:\n"
267 " try:\n"
262 " ip.magic(%r)\n"
268 " ip.magic(%r)\n"
263 " except NameError as e:\n"
269 " except NameError as e:\n"
264 " print(i)\n"
270 " print(i)\n"
265 " break\n" % ("run " + empty.fname)
271 " break\n" % ("run " + empty.fname)
266 )
272 )
267 self.mktmp(src)
273 self.mktmp(src)
268 _ip.magic("run %s" % self.fname)
274 _ip.magic("run %s" % self.fname)
269 _ip.run_cell("ip == get_ipython()")
275 _ip.run_cell("ip == get_ipython()")
270 assert _ip.user_ns["i"] == 4
276 assert _ip.user_ns["i"] == 4
271
277
272 def test_run_second(self):
278 def test_run_second(self):
273 """Test that running a second file doesn't clobber the first, gh-3547"""
279 """Test that running a second file doesn't clobber the first, gh-3547"""
274 self.mktmp("avar = 1\n" "def afunc():\n" " return avar\n")
280 self.mktmp("avar = 1\n" "def afunc():\n" " return avar\n")
275
281
276 with tt.TempFileMixin() as empty:
282 with tt.TempFileMixin() as empty:
277 empty.mktmp("")
283 empty.mktmp("")
278
284
279 _ip.magic("run %s" % self.fname)
285 _ip.magic("run %s" % self.fname)
280 _ip.magic("run %s" % empty.fname)
286 _ip.magic("run %s" % empty.fname)
281 assert _ip.user_ns["afunc"]() == 1
287 assert _ip.user_ns["afunc"]() == 1
282
288
283 @dec.skip_win32
289 @dec.skip_win32
284 def test_tclass(self):
290 def test_tclass(self):
285 mydir = os.path.dirname(__file__)
291 mydir = os.path.dirname(__file__)
286 tc = os.path.join(mydir, 'tclass')
292 tc = os.path.join(mydir, 'tclass')
287 src = ("%%run '%s' C-first\n"
293 src = ("%%run '%s' C-first\n"
288 "%%run '%s' C-second\n"
294 "%%run '%s' C-second\n"
289 "%%run '%s' C-third\n") % (tc, tc, tc)
295 "%%run '%s' C-third\n") % (tc, tc, tc)
290 self.mktmp(src, '.ipy')
296 self.mktmp(src, '.ipy')
291 out = """\
297 out = """\
292 ARGV 1-: ['C-first']
298 ARGV 1-: ['C-first']
293 ARGV 1-: ['C-second']
299 ARGV 1-: ['C-second']
294 tclass.py: deleting object: C-first
300 tclass.py: deleting object: C-first
295 ARGV 1-: ['C-third']
301 ARGV 1-: ['C-third']
296 tclass.py: deleting object: C-second
302 tclass.py: deleting object: C-second
297 tclass.py: deleting object: C-third
303 tclass.py: deleting object: C-third
298 """
304 """
299 err = None
305 err = None
300 tt.ipexec_validate(self.fname, out, err)
306 tt.ipexec_validate(self.fname, out, err)
301
307
302 def test_run_i_after_reset(self):
308 def test_run_i_after_reset(self):
303 """Check that %run -i still works after %reset (gh-693)"""
309 """Check that %run -i still works after %reset (gh-693)"""
304 src = "yy = zz\n"
310 src = "yy = zz\n"
305 self.mktmp(src)
311 self.mktmp(src)
306 _ip.run_cell("zz = 23")
312 _ip.run_cell("zz = 23")
307 try:
313 try:
308 _ip.magic("run -i %s" % self.fname)
314 _ip.magic("run -i %s" % self.fname)
309 assert _ip.user_ns["yy"] == 23
315 assert _ip.user_ns["yy"] == 23
310 finally:
316 finally:
311 _ip.magic('reset -f')
317 _ip.magic('reset -f')
312
318
313 _ip.run_cell("zz = 23")
319 _ip.run_cell("zz = 23")
314 try:
320 try:
315 _ip.magic("run -i %s" % self.fname)
321 _ip.magic("run -i %s" % self.fname)
316 assert _ip.user_ns["yy"] == 23
322 assert _ip.user_ns["yy"] == 23
317 finally:
323 finally:
318 _ip.magic('reset -f')
324 _ip.magic('reset -f')
319
325
320 def test_unicode(self):
326 def test_unicode(self):
321 """Check that files in odd encodings are accepted."""
327 """Check that files in odd encodings are accepted."""
322 mydir = os.path.dirname(__file__)
328 mydir = os.path.dirname(__file__)
323 na = os.path.join(mydir, 'nonascii.py')
329 na = os.path.join(mydir, 'nonascii.py')
324 _ip.magic('run "%s"' % na)
330 _ip.magic('run "%s"' % na)
325 assert _ip.user_ns["u"] == "Ўт№Ф"
331 assert _ip.user_ns["u"] == "Ўт№Ф"
326
332
327 def test_run_py_file_attribute(self):
333 def test_run_py_file_attribute(self):
328 """Test handling of `__file__` attribute in `%run <file>.py`."""
334 """Test handling of `__file__` attribute in `%run <file>.py`."""
329 src = "t = __file__\n"
335 src = "t = __file__\n"
330 self.mktmp(src)
336 self.mktmp(src)
331 _missing = object()
337 _missing = object()
332 file1 = _ip.user_ns.get('__file__', _missing)
338 file1 = _ip.user_ns.get('__file__', _missing)
333 _ip.magic('run %s' % self.fname)
339 _ip.magic('run %s' % self.fname)
334 file2 = _ip.user_ns.get('__file__', _missing)
340 file2 = _ip.user_ns.get('__file__', _missing)
335
341
336 # Check that __file__ was equal to the filename in the script's
342 # Check that __file__ was equal to the filename in the script's
337 # namespace.
343 # namespace.
338 assert _ip.user_ns["t"] == self.fname
344 assert _ip.user_ns["t"] == self.fname
339
345
340 # Check that __file__ was not leaked back into user_ns.
346 # Check that __file__ was not leaked back into user_ns.
341 assert file1 == file2
347 assert file1 == file2
342
348
343 def test_run_ipy_file_attribute(self):
349 def test_run_ipy_file_attribute(self):
344 """Test handling of `__file__` attribute in `%run <file.ipy>`."""
350 """Test handling of `__file__` attribute in `%run <file.ipy>`."""
345 src = "t = __file__\n"
351 src = "t = __file__\n"
346 self.mktmp(src, ext='.ipy')
352 self.mktmp(src, ext='.ipy')
347 _missing = object()
353 _missing = object()
348 file1 = _ip.user_ns.get('__file__', _missing)
354 file1 = _ip.user_ns.get('__file__', _missing)
349 _ip.magic('run %s' % self.fname)
355 _ip.magic('run %s' % self.fname)
350 file2 = _ip.user_ns.get('__file__', _missing)
356 file2 = _ip.user_ns.get('__file__', _missing)
351
357
352 # Check that __file__ was equal to the filename in the script's
358 # Check that __file__ was equal to the filename in the script's
353 # namespace.
359 # namespace.
354 assert _ip.user_ns["t"] == self.fname
360 assert _ip.user_ns["t"] == self.fname
355
361
356 # Check that __file__ was not leaked back into user_ns.
362 # Check that __file__ was not leaked back into user_ns.
357 assert file1 == file2
363 assert file1 == file2
358
364
359 def test_run_formatting(self):
365 def test_run_formatting(self):
360 """ Test that %run -t -N<N> does not raise a TypeError for N > 1."""
366 """ Test that %run -t -N<N> does not raise a TypeError for N > 1."""
361 src = "pass"
367 src = "pass"
362 self.mktmp(src)
368 self.mktmp(src)
363 _ip.magic('run -t -N 1 %s' % self.fname)
369 _ip.magic('run -t -N 1 %s' % self.fname)
364 _ip.magic('run -t -N 10 %s' % self.fname)
370 _ip.magic('run -t -N 10 %s' % self.fname)
365
371
366 def test_ignore_sys_exit(self):
372 def test_ignore_sys_exit(self):
367 """Test the -e option to ignore sys.exit()"""
373 """Test the -e option to ignore sys.exit()"""
368 src = "import sys; sys.exit(1)"
374 src = "import sys; sys.exit(1)"
369 self.mktmp(src)
375 self.mktmp(src)
370 with tt.AssertPrints('SystemExit'):
376 with tt.AssertPrints('SystemExit'):
371 _ip.magic('run %s' % self.fname)
377 _ip.magic('run %s' % self.fname)
372
378
373 with tt.AssertNotPrints('SystemExit'):
379 with tt.AssertNotPrints('SystemExit'):
374 _ip.magic('run -e %s' % self.fname)
380 _ip.magic('run -e %s' % self.fname)
375
381
376 def test_run_nb(self):
382 def test_run_nb(self):
377 """Test %run notebook.ipynb"""
383 """Test %run notebook.ipynb"""
378 from nbformat import v4, writes
384 from nbformat import v4, writes
379 nb = v4.new_notebook(
385 nb = v4.new_notebook(
380 cells=[
386 cells=[
381 v4.new_markdown_cell("The Ultimate Question of Everything"),
387 v4.new_markdown_cell("The Ultimate Question of Everything"),
382 v4.new_code_cell("answer=42")
388 v4.new_code_cell("answer=42")
383 ]
389 ]
384 )
390 )
385 src = writes(nb, version=4)
391 src = writes(nb, version=4)
386 self.mktmp(src, ext='.ipynb')
392 self.mktmp(src, ext='.ipynb')
387
393
388 _ip.magic("run %s" % self.fname)
394 _ip.magic("run %s" % self.fname)
389
395
390 assert _ip.user_ns["answer"] == 42
396 assert _ip.user_ns["answer"] == 42
391
397
392 def test_run_nb_error(self):
398 def test_run_nb_error(self):
393 """Test %run notebook.ipynb error"""
399 """Test %run notebook.ipynb error"""
394 from nbformat import v4, writes
400 from nbformat import v4, writes
395 # %run when a file name isn't provided
401 # %run when a file name isn't provided
396 pytest.raises(Exception, _ip.magic, "run")
402 pytest.raises(Exception, _ip.magic, "run")
397
403
398 # %run when a file doesn't exist
404 # %run when a file doesn't exist
399 pytest.raises(Exception, _ip.magic, "run foobar.ipynb")
405 pytest.raises(Exception, _ip.magic, "run foobar.ipynb")
400
406
401 # %run on a notebook with an error
407 # %run on a notebook with an error
402 nb = v4.new_notebook(
408 nb = v4.new_notebook(
403 cells=[
409 cells=[
404 v4.new_code_cell("0/0")
410 v4.new_code_cell("0/0")
405 ]
411 ]
406 )
412 )
407 src = writes(nb, version=4)
413 src = writes(nb, version=4)
408 self.mktmp(src, ext='.ipynb')
414 self.mktmp(src, ext='.ipynb')
409 pytest.raises(Exception, _ip.magic, "run %s" % self.fname)
415 pytest.raises(Exception, _ip.magic, "run %s" % self.fname)
410
416
411 def test_file_options(self):
417 def test_file_options(self):
412 src = ('import sys\n'
418 src = ('import sys\n'
413 'a = " ".join(sys.argv[1:])\n')
419 'a = " ".join(sys.argv[1:])\n')
414 self.mktmp(src)
420 self.mktmp(src)
415 test_opts = "-x 3 --verbose"
421 test_opts = "-x 3 --verbose"
416 _ip.run_line_magic("run", "{0} {1}".format(self.fname, test_opts))
422 _ip.run_line_magic("run", "{0} {1}".format(self.fname, test_opts))
417 assert _ip.user_ns["a"] == test_opts
423 assert _ip.user_ns["a"] == test_opts
418
424
419
425
420 class TestMagicRunWithPackage(unittest.TestCase):
426 class TestMagicRunWithPackage(unittest.TestCase):
421
427
422 def writefile(self, name, content):
428 def writefile(self, name, content):
423 path = os.path.join(self.tempdir.name, name)
429 path = os.path.join(self.tempdir.name, name)
424 d = os.path.dirname(path)
430 d = os.path.dirname(path)
425 if not os.path.isdir(d):
431 if not os.path.isdir(d):
426 os.makedirs(d)
432 os.makedirs(d)
427 with open(path, 'w') as f:
433 with open(path, 'w') as f:
428 f.write(textwrap.dedent(content))
434 f.write(textwrap.dedent(content))
429
435
430 def setUp(self):
436 def setUp(self):
431 self.package = package = 'tmp{0}'.format(''.join([random.choice(string.ascii_letters) for i in range(10)]))
437 self.package = package = 'tmp{0}'.format(''.join([random.choice(string.ascii_letters) for i in range(10)]))
432 """Temporary (probably) valid python package name."""
438 """Temporary (probably) valid python package name."""
433
439
434 self.value = int(random.random() * 10000)
440 self.value = int(random.random() * 10000)
435
441
436 self.tempdir = TemporaryDirectory()
442 self.tempdir = TemporaryDirectory()
437 self.__orig_cwd = os.getcwd()
443 self.__orig_cwd = os.getcwd()
438 sys.path.insert(0, self.tempdir.name)
444 sys.path.insert(0, self.tempdir.name)
439
445
440 self.writefile(os.path.join(package, '__init__.py'), '')
446 self.writefile(os.path.join(package, '__init__.py'), '')
441 self.writefile(os.path.join(package, 'sub.py'), """
447 self.writefile(os.path.join(package, 'sub.py'), """
442 x = {0!r}
448 x = {0!r}
443 """.format(self.value))
449 """.format(self.value))
444 self.writefile(os.path.join(package, 'relative.py'), """
450 self.writefile(os.path.join(package, 'relative.py'), """
445 from .sub import x
451 from .sub import x
446 """)
452 """)
447 self.writefile(os.path.join(package, 'absolute.py'), """
453 self.writefile(os.path.join(package, 'absolute.py'), """
448 from {0}.sub import x
454 from {0}.sub import x
449 """.format(package))
455 """.format(package))
450 self.writefile(os.path.join(package, 'args.py'), """
456 self.writefile(os.path.join(package, 'args.py'), """
451 import sys
457 import sys
452 a = " ".join(sys.argv[1:])
458 a = " ".join(sys.argv[1:])
453 """.format(package))
459 """.format(package))
454
460
455 def tearDown(self):
461 def tearDown(self):
456 os.chdir(self.__orig_cwd)
462 os.chdir(self.__orig_cwd)
457 sys.path[:] = [p for p in sys.path if p != self.tempdir.name]
463 sys.path[:] = [p for p in sys.path if p != self.tempdir.name]
458 self.tempdir.cleanup()
464 self.tempdir.cleanup()
459
465
460 def check_run_submodule(self, submodule, opts=''):
466 def check_run_submodule(self, submodule, opts=''):
461 _ip.user_ns.pop('x', None)
467 _ip.user_ns.pop('x', None)
462 _ip.magic('run {2} -m {0}.{1}'.format(self.package, submodule, opts))
468 _ip.magic('run {2} -m {0}.{1}'.format(self.package, submodule, opts))
463 self.assertEqual(_ip.user_ns['x'], self.value,
469 self.assertEqual(_ip.user_ns['x'], self.value,
464 'Variable `x` is not loaded from module `{0}`.'
470 'Variable `x` is not loaded from module `{0}`.'
465 .format(submodule))
471 .format(submodule))
466
472
467 def test_run_submodule_with_absolute_import(self):
473 def test_run_submodule_with_absolute_import(self):
468 self.check_run_submodule('absolute')
474 self.check_run_submodule('absolute')
469
475
470 def test_run_submodule_with_relative_import(self):
476 def test_run_submodule_with_relative_import(self):
471 """Run submodule that has a relative import statement (#2727)."""
477 """Run submodule that has a relative import statement (#2727)."""
472 self.check_run_submodule('relative')
478 self.check_run_submodule('relative')
473
479
474 def test_prun_submodule_with_absolute_import(self):
480 def test_prun_submodule_with_absolute_import(self):
475 self.check_run_submodule('absolute', '-p')
481 self.check_run_submodule('absolute', '-p')
476
482
477 def test_prun_submodule_with_relative_import(self):
483 def test_prun_submodule_with_relative_import(self):
478 self.check_run_submodule('relative', '-p')
484 self.check_run_submodule('relative', '-p')
479
485
480 def with_fake_debugger(func):
486 def with_fake_debugger(func):
481 @functools.wraps(func)
487 @functools.wraps(func)
482 def wrapper(*args, **kwds):
488 def wrapper(*args, **kwds):
483 with patch.object(debugger.Pdb, 'run', staticmethod(eval)):
489 with patch.object(debugger.Pdb, 'run', staticmethod(eval)):
484 return func(*args, **kwds)
490 return func(*args, **kwds)
485 return wrapper
491 return wrapper
486
492
487 @with_fake_debugger
493 @with_fake_debugger
488 def test_debug_run_submodule_with_absolute_import(self):
494 def test_debug_run_submodule_with_absolute_import(self):
489 self.check_run_submodule('absolute', '-d')
495 self.check_run_submodule('absolute', '-d')
490
496
491 @with_fake_debugger
497 @with_fake_debugger
492 def test_debug_run_submodule_with_relative_import(self):
498 def test_debug_run_submodule_with_relative_import(self):
493 self.check_run_submodule('relative', '-d')
499 self.check_run_submodule('relative', '-d')
494
500
495 def test_module_options(self):
501 def test_module_options(self):
496 _ip.user_ns.pop("a", None)
502 _ip.user_ns.pop("a", None)
497 test_opts = "-x abc -m test"
503 test_opts = "-x abc -m test"
498 _ip.run_line_magic("run", "-m {0}.args {1}".format(self.package, test_opts))
504 _ip.run_line_magic("run", "-m {0}.args {1}".format(self.package, test_opts))
499 assert _ip.user_ns["a"] == test_opts
505 assert _ip.user_ns["a"] == test_opts
500
506
501 def test_module_options_with_separator(self):
507 def test_module_options_with_separator(self):
502 _ip.user_ns.pop("a", None)
508 _ip.user_ns.pop("a", None)
503 test_opts = "-x abc -m test"
509 test_opts = "-x abc -m test"
504 _ip.run_line_magic("run", "-m {0}.args -- {1}".format(self.package, test_opts))
510 _ip.run_line_magic("run", "-m {0}.args -- {1}".format(self.package, test_opts))
505 assert _ip.user_ns["a"] == test_opts
511 assert _ip.user_ns["a"] == test_opts
506
512
507
513
508 def test_run__name__():
514 def test_run__name__():
509 with TemporaryDirectory() as td:
515 with TemporaryDirectory() as td:
510 path = pjoin(td, 'foo.py')
516 path = pjoin(td, 'foo.py')
511 with open(path, 'w') as f:
517 with open(path, 'w') as f:
512 f.write("q = __name__")
518 f.write("q = __name__")
513
519
514 _ip.user_ns.pop("q", None)
520 _ip.user_ns.pop("q", None)
515 _ip.magic("run {}".format(path))
521 _ip.magic("run {}".format(path))
516 assert _ip.user_ns.pop("q") == "__main__"
522 assert _ip.user_ns.pop("q") == "__main__"
517
523
518 _ip.magic("run -n {}".format(path))
524 _ip.magic("run -n {}".format(path))
519 assert _ip.user_ns.pop("q") == "foo"
525 assert _ip.user_ns.pop("q") == "foo"
520
526
521 try:
527 try:
522 _ip.magic("run -i -n {}".format(path))
528 _ip.magic("run -i -n {}".format(path))
523 assert _ip.user_ns.pop("q") == "foo"
529 assert _ip.user_ns.pop("q") == "foo"
524 finally:
530 finally:
525 _ip.magic('reset -f')
531 _ip.magic('reset -f')
526
532
527
533
528 def test_run_tb():
534 def test_run_tb():
529 """Test traceback offset in %run"""
535 """Test traceback offset in %run"""
530 with TemporaryDirectory() as td:
536 with TemporaryDirectory() as td:
531 path = pjoin(td, 'foo.py')
537 path = pjoin(td, 'foo.py')
532 with open(path, 'w') as f:
538 with open(path, 'w') as f:
533 f.write('\n'.join([
539 f.write('\n'.join([
534 "def foo():",
540 "def foo():",
535 " return bar()",
541 " return bar()",
536 "def bar():",
542 "def bar():",
537 " raise RuntimeError('hello!')",
543 " raise RuntimeError('hello!')",
538 "foo()",
544 "foo()",
539 ]))
545 ]))
540 with capture_output() as io:
546 with capture_output() as io:
541 _ip.magic('run {}'.format(path))
547 _ip.magic('run {}'.format(path))
542 out = io.stdout
548 out = io.stdout
543 assert "execfile" not in out
549 assert "execfile" not in out
544 assert "RuntimeError" in out
550 assert "RuntimeError" in out
545 assert out.count("---->") == 3
551 assert out.count("---->") == 3
546 del ip.user_ns['bar']
552 del ip.user_ns['bar']
547 del ip.user_ns['foo']
553 del ip.user_ns['foo']
548
554
549
555
550 def test_multiprocessing_run():
556 def test_multiprocessing_run():
551 """Set we can run mutiprocesgin without messing up up main namespace
557 """Set we can run mutiprocesgin without messing up up main namespace
552
558
553 Note that import `nose.tools as nt` mdify the value s
559 Note that import `nose.tools as nt` mdify the value s
554 sys.module['__mp_main__'] so we need to temporarily set it to None to test
560 sys.module['__mp_main__'] so we need to temporarily set it to None to test
555 the issue.
561 the issue.
556 """
562 """
557 with TemporaryDirectory() as td:
563 with TemporaryDirectory() as td:
558 mpm = sys.modules.get('__mp_main__')
564 mpm = sys.modules.get('__mp_main__')
559 assert mpm is not None
560 sys.modules['__mp_main__'] = None
565 sys.modules['__mp_main__'] = None
561 try:
566 try:
562 path = pjoin(td, 'test.py')
567 path = pjoin(td, 'test.py')
563 with open(path, 'w') as f:
568 with open(path, 'w') as f:
564 f.write("import multiprocessing\nprint('hoy')")
569 f.write("import multiprocessing\nprint('hoy')")
565 with capture_output() as io:
570 with capture_output() as io:
566 _ip.run_line_magic('run', path)
571 _ip.run_line_magic('run', path)
567 _ip.run_cell("i_m_undefined")
572 _ip.run_cell("i_m_undefined")
568 out = io.stdout
573 out = io.stdout
569 assert "hoy" in out
574 assert "hoy" in out
570 assert "AttributeError" not in out
575 assert "AttributeError" not in out
571 assert "NameError" in out
576 assert "NameError" in out
572 assert out.count("---->") == 1
577 assert out.count("---->") == 1
573 except:
578 except:
574 raise
579 raise
575 finally:
580 finally:
576 sys.modules['__mp_main__'] = mpm
581 sys.modules['__mp_main__'] = mpm
577
582
578 @dec.knownfailureif(sys.platform == 'win32', "writes to io.stdout aren't captured on Windows")
583
579 def test_script_tb():
584 def test_script_tb():
580 """Test traceback offset in `ipython script.py`"""
585 """Test traceback offset in `ipython script.py`"""
581 with TemporaryDirectory() as td:
586 with TemporaryDirectory() as td:
582 path = pjoin(td, 'foo.py')
587 path = pjoin(td, 'foo.py')
583 with open(path, 'w') as f:
588 with open(path, 'w') as f:
584 f.write('\n'.join([
589 f.write('\n'.join([
585 "def foo():",
590 "def foo():",
586 " return bar()",
591 " return bar()",
587 "def bar():",
592 "def bar():",
588 " raise RuntimeError('hello!')",
593 " raise RuntimeError('hello!')",
589 "foo()",
594 "foo()",
590 ]))
595 ]))
591 out, err = tt.ipexec(path)
596 out, err = tt.ipexec(path)
592 assert "execfile" not in out
597 assert "execfile" not in out
593 assert "RuntimeError" in out
598 assert "RuntimeError" in out
594 assert out.count("---->") == 3
599 assert out.count("---->") == 3
@@ -1,49 +1,20 b''
1 """Testing support (tools to test IPython itself).
1 """Testing support (tools to test IPython itself).
2 """
2 """
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2009-2011 The IPython Development Team
5 # Copyright (C) 2009-2011 The IPython Development Team
6 #
6 #
7 # Distributed under the terms of the BSD License. The full license is in
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11
11
12 import os
12 import os
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Functions
16 #-----------------------------------------------------------------------------
17
18 # User-level entry point for testing
19 def test(**kwargs):
20 """Run the entire IPython test suite.
21
22 Any of the options for run_iptestall() may be passed as keyword arguments.
23
24 For example::
25
26 IPython.test(testgroups=['lib', 'config', 'utils'], fast=2)
27
28 will run those three sections of the test suite, using two processes.
29 """
30
31 # Do the import internally, so that this function doesn't increase total
32 # import time
33 from .iptestcontroller import run_iptestall, default_options
34 options = default_options()
35 for name, val in kwargs.items():
36 setattr(options, name, val)
37 run_iptestall(options)
38
39 #-----------------------------------------------------------------------------
40 # Constants
15 # Constants
41 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
42
17
43 # We scale all timeouts via this factor, slow machines can increase it
18 # We scale all timeouts via this factor, slow machines can increase it
44 IPYTHON_TESTING_TIMEOUT_SCALE = float(os.getenv(
19 IPYTHON_TESTING_TIMEOUT_SCALE = float(os.getenv(
45 'IPYTHON_TESTING_TIMEOUT_SCALE', 1))
20 'IPYTHON_TESTING_TIMEOUT_SCALE', 1))
46
47 # So nose doesn't try to run this as a test itself and we end up with an
48 # infinite test loop
49 test.__test__ = False
@@ -1,399 +1,342 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Decorators for labeling test objects.
2 """Decorators for labeling test objects.
3
3
4 Decorators that merely return a modified version of the original function
4 Decorators that merely return a modified version of the original function
5 object are straightforward. Decorators that return a new function object need
5 object are straightforward. Decorators that return a new function object need
6 to use nose.tools.make_decorator(original_function)(decorator) in returning the
6 to use nose.tools.make_decorator(original_function)(decorator) in returning the
7 decorator, in order to preserve metadata such as function name, setup and
7 decorator, in order to preserve metadata such as function name, setup and
8 teardown functions and so on - see nose.tools for more information.
8 teardown functions and so on - see nose.tools for more information.
9
9
10 This module provides a set of useful decorators meant to be ready to use in
10 This module provides a set of useful decorators meant to be ready to use in
11 your own tests. See the bottom of the file for the ready-made ones, and if you
11 your own tests. See the bottom of the file for the ready-made ones, and if you
12 find yourself writing a new one that may be of generic use, add it here.
12 find yourself writing a new one that may be of generic use, add it here.
13
13
14 Included decorators:
14 Included decorators:
15
15
16
16
17 Lightweight testing that remains unittest-compatible.
17 Lightweight testing that remains unittest-compatible.
18
18
19 - An @as_unittest decorator can be used to tag any normal parameter-less
19 - An @as_unittest decorator can be used to tag any normal parameter-less
20 function as a unittest TestCase. Then, both nose and normal unittest will
20 function as a unittest TestCase. Then, both nose and normal unittest will
21 recognize it as such. This will make it easier to migrate away from Nose if
21 recognize it as such. This will make it easier to migrate away from Nose if
22 we ever need/want to while maintaining very lightweight tests.
22 we ever need/want to while maintaining very lightweight tests.
23
23
24 NOTE: This file contains IPython-specific decorators. Using the machinery in
24 NOTE: This file contains IPython-specific decorators. Using the machinery in
25 IPython.external.decorators, we import either numpy.testing.decorators if numpy is
25 IPython.external.decorators, we import either numpy.testing.decorators if numpy is
26 available, OR use equivalent code in IPython.external._decorators, which
26 available, OR use equivalent code in IPython.external._decorators, which
27 we've copied verbatim from numpy.
27 we've copied verbatim from numpy.
28
28
29 """
29 """
30
30
31 # Copyright (c) IPython Development Team.
31 # Copyright (c) IPython Development Team.
32 # Distributed under the terms of the Modified BSD License.
32 # Distributed under the terms of the Modified BSD License.
33
33
34 import os
34 import os
35 import shutil
35 import shutil
36 import sys
36 import sys
37 import tempfile
37 import tempfile
38 import unittest
38 import unittest
39 import warnings
39 import warnings
40 from importlib import import_module
40 from importlib import import_module
41
41
42 from decorator import decorator
42 from decorator import decorator
43
43
44 # Expose the unittest-driven decorators
44 # Expose the unittest-driven decorators
45 from .ipunittest import ipdoctest, ipdocstring
45 from .ipunittest import ipdoctest, ipdocstring
46
46
47 # Grab the numpy-specific decorators which we keep in a file that we
48 # occasionally update from upstream: decorators.py is a copy of
49 # numpy.testing.decorators, we expose all of it here.
50 from IPython.external.decorators import knownfailureif
51
52 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
53 # Classes and functions
48 # Classes and functions
54 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
55
50
56 # Simple example of the basic idea
51 # Simple example of the basic idea
57 def as_unittest(func):
52 def as_unittest(func):
58 """Decorator to make a simple function into a normal test via unittest."""
53 """Decorator to make a simple function into a normal test via unittest."""
59 class Tester(unittest.TestCase):
54 class Tester(unittest.TestCase):
60 def test(self):
55 def test(self):
61 func()
56 func()
62
57
63 Tester.__name__ = func.__name__
58 Tester.__name__ = func.__name__
64
59
65 return Tester
60 return Tester
66
61
67 # Utility functions
62 # Utility functions
68
63
69 def apply_wrapper(wrapper, func):
64 def apply_wrapper(wrapper, func):
70 """Apply a wrapper to a function for decoration.
65 """Apply a wrapper to a function for decoration.
71
66
72 This mixes Michele Simionato's decorator tool with nose's make_decorator,
67 This mixes Michele Simionato's decorator tool with nose's make_decorator,
73 to apply a wrapper in a decorator so that all nose attributes, as well as
68 to apply a wrapper in a decorator so that all nose attributes, as well as
74 function signature and other properties, survive the decoration cleanly.
69 function signature and other properties, survive the decoration cleanly.
75 This will ensure that wrapped functions can still be well introspected via
70 This will ensure that wrapped functions can still be well introspected via
76 IPython, for example.
71 IPython, for example.
77 """
72 """
78 warnings.warn("The function `apply_wrapper` is deprecated since IPython 4.0",
73 warnings.warn("The function `apply_wrapper` is deprecated since IPython 4.0",
79 DeprecationWarning, stacklevel=2)
74 DeprecationWarning, stacklevel=2)
80 import nose.tools
75 import nose.tools
81
76
82 return decorator(wrapper,nose.tools.make_decorator(func)(wrapper))
77 return decorator(wrapper,nose.tools.make_decorator(func)(wrapper))
83
78
84
79
85 def make_label_dec(label, ds=None):
80 def make_label_dec(label, ds=None):
86 """Factory function to create a decorator that applies one or more labels.
81 """Factory function to create a decorator that applies one or more labels.
87
82
88 Parameters
83 Parameters
89 ----------
84 ----------
90 label : string or sequence
85 label : string or sequence
91 One or more labels that will be applied by the decorator to the functions
86 One or more labels that will be applied by the decorator to the functions
92 it decorates. Labels are attributes of the decorated function with their
87 it decorates. Labels are attributes of the decorated function with their
93 value set to True.
88 value set to True.
94
89
95 ds : string
90 ds : string
96 An optional docstring for the resulting decorator. If not given, a
91 An optional docstring for the resulting decorator. If not given, a
97 default docstring is auto-generated.
92 default docstring is auto-generated.
98
93
99 Returns
94 Returns
100 -------
95 -------
101 A decorator.
96 A decorator.
102
97
103 Examples
98 Examples
104 --------
99 --------
105
100
106 A simple labeling decorator:
101 A simple labeling decorator:
107
102
108 >>> slow = make_label_dec('slow')
103 >>> slow = make_label_dec('slow')
109 >>> slow.__doc__
104 >>> slow.__doc__
110 "Labels a test as 'slow'."
105 "Labels a test as 'slow'."
111
106
112 And one that uses multiple labels and a custom docstring:
107 And one that uses multiple labels and a custom docstring:
113
108
114 >>> rare = make_label_dec(['slow','hard'],
109 >>> rare = make_label_dec(['slow','hard'],
115 ... "Mix labels 'slow' and 'hard' for rare tests.")
110 ... "Mix labels 'slow' and 'hard' for rare tests.")
116 >>> rare.__doc__
111 >>> rare.__doc__
117 "Mix labels 'slow' and 'hard' for rare tests."
112 "Mix labels 'slow' and 'hard' for rare tests."
118
113
119 Now, let's test using this one:
114 Now, let's test using this one:
120 >>> @rare
115 >>> @rare
121 ... def f(): pass
116 ... def f(): pass
122 ...
117 ...
123 >>>
118 >>>
124 >>> f.slow
119 >>> f.slow
125 True
120 True
126 >>> f.hard
121 >>> f.hard
127 True
122 True
128 """
123 """
129
124
130 warnings.warn("The function `make_label_dec` is deprecated since IPython 4.0",
125 warnings.warn("The function `make_label_dec` is deprecated since IPython 4.0",
131 DeprecationWarning, stacklevel=2)
126 DeprecationWarning, stacklevel=2)
132 if isinstance(label, str):
127 if isinstance(label, str):
133 labels = [label]
128 labels = [label]
134 else:
129 else:
135 labels = label
130 labels = label
136
131
137 # Validate that the given label(s) are OK for use in setattr() by doing a
132 # Validate that the given label(s) are OK for use in setattr() by doing a
138 # dry run on a dummy function.
133 # dry run on a dummy function.
139 tmp = lambda : None
134 tmp = lambda : None
140 for label in labels:
135 for label in labels:
141 setattr(tmp,label,True)
136 setattr(tmp,label,True)
142
137
143 # This is the actual decorator we'll return
138 # This is the actual decorator we'll return
144 def decor(f):
139 def decor(f):
145 for label in labels:
140 for label in labels:
146 setattr(f,label,True)
141 setattr(f,label,True)
147 return f
142 return f
148
143
149 # Apply the user's docstring, or autogenerate a basic one
144 # Apply the user's docstring, or autogenerate a basic one
150 if ds is None:
145 if ds is None:
151 ds = "Labels a test as %r." % label
146 ds = "Labels a test as %r." % label
152 decor.__doc__ = ds
147 decor.__doc__ = ds
153
148
154 return decor
149 return decor
155
150
156
151
157 def skip_iptest_but_not_pytest(f):
152 def skip_iptest_but_not_pytest(f):
158 """
153 """
159 Warning this will make the test invisible to iptest.
154 Warning this will make the test invisible to iptest.
160 """
155 """
161 import os
156 import os
162
157
163 if os.environ.get("IPTEST_WORKING_DIR", None) is not None:
158 if os.environ.get("IPTEST_WORKING_DIR", None) is not None:
164 f.__test__ = False
159 f.__test__ = False
165 return f
160 return f
166
161
167
162
168 # Inspired by numpy's skipif, but uses the full apply_wrapper utility to
169 # preserve function metadata better and allows the skip condition to be a
170 # callable.
171 def skipif(skip_condition, msg=None):
163 def skipif(skip_condition, msg=None):
172 ''' Make function raise SkipTest exception if skip_condition is true
164 """Make function raise SkipTest exception if skip_condition is true
173
165
174 Parameters
166 Parameters
175 ----------
167 ----------
176
168
177 skip_condition : bool or callable
169 skip_condition : bool or callable
178 Flag to determine whether to skip test. If the condition is a
170 Flag to determine whether to skip test. If the condition is a
179 callable, it is used at runtime to dynamically make the decision. This
171 callable, it is used at runtime to dynamically make the decision. This
180 is useful for tests that may require costly imports, to delay the cost
172 is useful for tests that may require costly imports, to delay the cost
181 until the test suite is actually executed.
173 until the test suite is actually executed.
182 msg : string
174 msg : string
183 Message to give on raising a SkipTest exception.
175 Message to give on raising a SkipTest exception.
184
176
185 Returns
177 Returns
186 -------
178 -------
187 decorator : function
179 decorator : function
188 Decorator, which, when applied to a function, causes SkipTest
180 Decorator, which, when applied to a function, causes SkipTest
189 to be raised when the skip_condition was True, and the function
181 to be raised when the skip_condition was True, and the function
190 to be called normally otherwise.
182 to be called normally otherwise.
183 """
184 if msg is None:
185 msg = "Test skipped due to test condition."
191
186
192 Notes
187 import pytest
193 -----
194 You will see from the code that we had to further decorate the
195 decorator with the nose.tools.make_decorator function in order to
196 transmit function name, and various other metadata.
197 '''
198
199 def skip_decorator(f):
200 # Local import to avoid a hard nose dependency and only incur the
201 # import time overhead at actual test-time.
202 import nose
203
204 # Allow for both boolean or callable skip conditions.
205 if callable(skip_condition):
206 skip_val = skip_condition
207 else:
208 skip_val = lambda : skip_condition
209
210 def get_msg(func,msg=None):
211 """Skip message with information about function being skipped."""
212 if msg is None: out = 'Test skipped due to test condition.'
213 else: out = msg
214 return "Skipping test: %s. %s" % (func.__name__,out)
215
216 # We need to define *two* skippers because Python doesn't allow both
217 # return with value and yield inside the same function.
218 def skipper_func(*args, **kwargs):
219 """Skipper for normal test functions."""
220 if skip_val():
221 raise nose.SkipTest(get_msg(f,msg))
222 else:
223 return f(*args, **kwargs)
224
225 def skipper_gen(*args, **kwargs):
226 """Skipper for test generators."""
227 if skip_val():
228 raise nose.SkipTest(get_msg(f,msg))
229 else:
230 for x in f(*args, **kwargs):
231 yield x
232
233 # Choose the right skipper to use when building the actual generator.
234 if nose.util.isgenerator(f):
235 skipper = skipper_gen
236 else:
237 skipper = skipper_func
238
188
239 return nose.tools.make_decorator(f)(skipper)
189 assert isinstance(skip_condition, bool)
190 return pytest.mark.skipif(skip_condition, reason=msg)
240
191
241 return skip_decorator
242
192
243 # A version with the condition set to true, common case just to attach a message
193 # A version with the condition set to true, common case just to attach a message
244 # to a skip decorator
194 # to a skip decorator
245 def skip(msg=None):
195 def skip(msg=None):
246 """Decorator factory - mark a test function for skipping from test suite.
196 """Decorator factory - mark a test function for skipping from test suite.
247
197
248 Parameters
198 Parameters
249 ----------
199 ----------
250 msg : string
200 msg : string
251 Optional message to be added.
201 Optional message to be added.
252
202
253 Returns
203 Returns
254 -------
204 -------
255 decorator : function
205 decorator : function
256 Decorator, which, when applied to a function, causes SkipTest
206 Decorator, which, when applied to a function, causes SkipTest
257 to be raised, with the optional message added.
207 to be raised, with the optional message added.
258 """
208 """
259 if msg and not isinstance(msg, str):
209 if msg and not isinstance(msg, str):
260 raise ValueError('invalid object passed to `@skip` decorator, did you '
210 raise ValueError('invalid object passed to `@skip` decorator, did you '
261 'meant `@skip()` with brackets ?')
211 'meant `@skip()` with brackets ?')
262 return skipif(True, msg)
212 return skipif(True, msg)
263
213
264
214
265 def onlyif(condition, msg):
215 def onlyif(condition, msg):
266 """The reverse from skipif, see skipif for details."""
216 """The reverse from skipif, see skipif for details."""
267
217
268 if callable(condition):
218 return skipif(not condition, msg)
269 skip_condition = lambda : not condition()
270 else:
271 skip_condition = lambda : not condition
272
273 return skipif(skip_condition, msg)
274
219
275 #-----------------------------------------------------------------------------
220 #-----------------------------------------------------------------------------
276 # Utility functions for decorators
221 # Utility functions for decorators
277 def module_not_available(module):
222 def module_not_available(module):
278 """Can module be imported? Returns true if module does NOT import.
223 """Can module be imported? Returns true if module does NOT import.
279
224
280 This is used to make a decorator to skip tests that require module to be
225 This is used to make a decorator to skip tests that require module to be
281 available, but delay the 'import numpy' to test execution time.
226 available, but delay the 'import numpy' to test execution time.
282 """
227 """
283 try:
228 try:
284 mod = import_module(module)
229 mod = import_module(module)
285 mod_not_avail = False
230 mod_not_avail = False
286 except ImportError:
231 except ImportError:
287 mod_not_avail = True
232 mod_not_avail = True
288
233
289 return mod_not_avail
234 return mod_not_avail
290
235
291
236
292 def decorated_dummy(dec, name):
237 def decorated_dummy(dec, name):
293 """Return a dummy function decorated with dec, with the given name.
238 """Return a dummy function decorated with dec, with the given name.
294
239
295 Examples
240 Examples
296 --------
241 --------
297 import IPython.testing.decorators as dec
242 import IPython.testing.decorators as dec
298 setup = dec.decorated_dummy(dec.skip_if_no_x11, __name__)
243 setup = dec.decorated_dummy(dec.skip_if_no_x11, __name__)
299 """
244 """
300 warnings.warn("The function `decorated_dummy` is deprecated since IPython 4.0",
245 warnings.warn("The function `decorated_dummy` is deprecated since IPython 4.0",
301 DeprecationWarning, stacklevel=2)
246 DeprecationWarning, stacklevel=2)
302 dummy = lambda: None
247 dummy = lambda: None
303 dummy.__name__ = name
248 dummy.__name__ = name
304 return dec(dummy)
249 return dec(dummy)
305
250
306 #-----------------------------------------------------------------------------
251 #-----------------------------------------------------------------------------
307 # Decorators for public use
252 # Decorators for public use
308
253
309 # Decorators to skip certain tests on specific platforms.
254 # Decorators to skip certain tests on specific platforms.
310 skip_win32 = skipif(sys.platform == 'win32',
255 skip_win32 = skipif(sys.platform == 'win32',
311 "This test does not run under Windows")
256 "This test does not run under Windows")
312 skip_linux = skipif(sys.platform.startswith('linux'),
257 skip_linux = skipif(sys.platform.startswith('linux'),
313 "This test does not run under Linux")
258 "This test does not run under Linux")
314 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
259 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
315
260
316
261
317 # Decorators to skip tests if not on specific platforms.
262 # Decorators to skip tests if not on specific platforms.
318 skip_if_not_win32 = skipif(sys.platform != 'win32',
263 skip_if_not_win32 = skipif(sys.platform != 'win32',
319 "This test only runs under Windows")
264 "This test only runs under Windows")
320 skip_if_not_linux = skipif(not sys.platform.startswith('linux'),
265 skip_if_not_linux = skipif(not sys.platform.startswith('linux'),
321 "This test only runs under Linux")
266 "This test only runs under Linux")
322 skip_if_not_osx = skipif(sys.platform != 'darwin',
267 skip_if_not_osx = skipif(sys.platform != 'darwin',
323 "This test only runs under OSX")
268 "This test only runs under OSX")
324
269
325
270
326 _x11_skip_cond = (sys.platform not in ('darwin', 'win32') and
271 _x11_skip_cond = (sys.platform not in ('darwin', 'win32') and
327 os.environ.get('DISPLAY', '') == '')
272 os.environ.get('DISPLAY', '') == '')
328 _x11_skip_msg = "Skipped under *nix when X11/XOrg not available"
273 _x11_skip_msg = "Skipped under *nix when X11/XOrg not available"
329
274
330 skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg)
275 skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg)
331
276
332
277
333 # Decorators to skip certain tests on specific platform/python combinations
278 # Decorators to skip certain tests on specific platform/python combinations
334 skip_win32_py38 = skipif(sys.version_info > (3,8) and os.name == 'nt')
279 skip_win32_py38 = skipif(sys.version_info > (3,8) and os.name == 'nt')
335
280
336
281
337 # not a decorator itself, returns a dummy function to be used as setup
282 # not a decorator itself, returns a dummy function to be used as setup
338 def skip_file_no_x11(name):
283 def skip_file_no_x11(name):
339 warnings.warn("The function `skip_file_no_x11` is deprecated since IPython 4.0",
284 warnings.warn("The function `skip_file_no_x11` is deprecated since IPython 4.0",
340 DeprecationWarning, stacklevel=2)
285 DeprecationWarning, stacklevel=2)
341 return decorated_dummy(skip_if_no_x11, name) if _x11_skip_cond else None
286 return decorated_dummy(skip_if_no_x11, name) if _x11_skip_cond else None
342
287
343 # Other skip decorators
288 # Other skip decorators
344
289
345 # generic skip without module
290 # generic skip without module
346 skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
291 skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
347
292
348 skipif_not_numpy = skip_without('numpy')
293 skipif_not_numpy = skip_without('numpy')
349
294
350 skipif_not_matplotlib = skip_without('matplotlib')
295 skipif_not_matplotlib = skip_without('matplotlib')
351
296
352 skipif_not_sympy = skip_without('sympy')
297 skipif_not_sympy = skip_without('sympy')
353
298
354 skip_known_failure = knownfailureif(True,'This test is known to fail')
355
356 # A null 'decorator', useful to make more readable code that needs to pick
299 # A null 'decorator', useful to make more readable code that needs to pick
357 # between different decorators based on OS or other conditions
300 # between different decorators based on OS or other conditions
358 null_deco = lambda f: f
301 null_deco = lambda f: f
359
302
360 # Some tests only run where we can use unicode paths. Note that we can't just
303 # Some tests only run where we can use unicode paths. Note that we can't just
361 # check os.path.supports_unicode_filenames, which is always False on Linux.
304 # check os.path.supports_unicode_filenames, which is always False on Linux.
362 try:
305 try:
363 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
306 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
364 except UnicodeEncodeError:
307 except UnicodeEncodeError:
365 unicode_paths = False
308 unicode_paths = False
366 else:
309 else:
367 unicode_paths = True
310 unicode_paths = True
368 f.close()
311 f.close()
369
312
370 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
313 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
371 "where we can use unicode in filenames."))
314 "where we can use unicode in filenames."))
372
315
373
316
374 def onlyif_cmds_exist(*commands):
317 def onlyif_cmds_exist(*commands):
375 """
318 """
376 Decorator to skip test when at least one of `commands` is not found.
319 Decorator to skip test when at least one of `commands` is not found.
377 """
320 """
378 for cmd in commands:
321 for cmd in commands:
379 reason = f"This test runs only if command '{cmd}' is installed"
322 reason = f"This test runs only if command '{cmd}' is installed"
380 if not shutil.which(cmd):
323 if not shutil.which(cmd):
381 if os.environ.get("IPTEST_WORKING_DIR", None) is not None:
324 if os.environ.get("IPTEST_WORKING_DIR", None) is not None:
382 return skip(reason)
325 return skip(reason)
383 else:
326 else:
384 import pytest
327 import pytest
385
328
386 return pytest.mark.skip(reason=reason)
329 return pytest.mark.skip(reason=reason)
387 return null_deco
330 return null_deco
388
331
389 def onlyif_any_cmd_exists(*commands):
332 def onlyif_any_cmd_exists(*commands):
390 """
333 """
391 Decorator to skip test unless at least one of `commands` is found.
334 Decorator to skip test unless at least one of `commands` is found.
392 """
335 """
393 warnings.warn("The function `onlyif_any_cmd_exists` is deprecated since IPython 4.0",
336 warnings.warn("The function `onlyif_any_cmd_exists` is deprecated since IPython 4.0",
394 DeprecationWarning, stacklevel=2)
337 DeprecationWarning, stacklevel=2)
395 for cmd in commands:
338 for cmd in commands:
396 if shutil.which(cmd):
339 if shutil.which(cmd):
397 return null_deco
340 return null_deco
398 return skip("This test runs only if one of the commands {0} "
341 return skip("This test runs only if one of the commands {0} "
399 "is installed".format(commands))
342 "is installed".format(commands))
@@ -1,762 +1,452 b''
1 """Nose Plugin that supports IPython doctests.
1 """Nose Plugin that supports IPython doctests.
2
2
3 Limitations:
3 Limitations:
4
4
5 - When generating examples for use as doctests, make sure that you have
5 - When generating examples for use as doctests, make sure that you have
6 pretty-printing OFF. This can be done either by setting the
6 pretty-printing OFF. This can be done either by setting the
7 ``PlainTextFormatter.pprint`` option in your configuration file to False, or
7 ``PlainTextFormatter.pprint`` option in your configuration file to False, or
8 by interactively disabling it with %Pprint. This is required so that IPython
8 by interactively disabling it with %Pprint. This is required so that IPython
9 output matches that of normal Python, which is used by doctest for internal
9 output matches that of normal Python, which is used by doctest for internal
10 execution.
10 execution.
11
11
12 - Do not rely on specific prompt numbers for results (such as using
12 - Do not rely on specific prompt numbers for results (such as using
13 '_34==True', for example). For IPython tests run via an external process the
13 '_34==True', for example). For IPython tests run via an external process the
14 prompt numbers may be different, and IPython tests run as normal python code
14 prompt numbers may be different, and IPython tests run as normal python code
15 won't even have these special _NN variables set at all.
15 won't even have these special _NN variables set at all.
16 """
16 """
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Module imports
19 # Module imports
20
20
21 # From the standard library
21 # From the standard library
22 import builtins as builtin_mod
23 import doctest
22 import doctest
24 import inspect
23 import inspect
25 import logging
24 import logging
26 import os
25 import os
27 import re
26 import re
28 import sys
29 from importlib import import_module
30 from io import StringIO
31
27
32 from testpath import modified_env
28 from testpath import modified_env
33
29
34 from inspect import getmodule
35
36 from pathlib import Path, PurePath
37
38 # We are overriding the default doctest runner, so we need to import a few
39 # things from doctest directly
40 from doctest import (REPORTING_FLAGS, REPORT_ONLY_FIRST_FAILURE,
41 _unittest_reportflags, DocTestRunner,
42 _extract_future_flags, pdb, _OutputRedirectingPdb,
43 _exception_traceback,
44 linecache)
45
46 # Third-party modules
47
48 from nose.plugins import doctests, Plugin
49 from nose.util import anyp, tolist
50
51 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
52 # Module globals and other constants
31 # Module globals and other constants
53 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
54
33
55 log = logging.getLogger(__name__)
34 log = logging.getLogger(__name__)
56
35
57
36
58 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
59 # Classes and functions
38 # Classes and functions
60 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
61
40
62 def is_extension_module(filename):
41 def is_extension_module(filename):
63 """Return whether the given filename is an extension module.
42 """Return whether the given filename is an extension module.
64
43
65 This simply checks that the extension is either .so or .pyd.
44 This simply checks that the extension is either .so or .pyd.
66 """
45 """
67 return os.path.splitext(filename)[1].lower() in ('.so','.pyd')
46 return os.path.splitext(filename)[1].lower() in ('.so','.pyd')
68
47
69
48
70 class DocTestSkip(object):
49 class DocTestSkip(object):
71 """Object wrapper for doctests to be skipped."""
50 """Object wrapper for doctests to be skipped."""
72
51
73 ds_skip = """Doctest to skip.
52 ds_skip = """Doctest to skip.
74 >>> 1 #doctest: +SKIP
53 >>> 1 #doctest: +SKIP
75 """
54 """
76
55
77 def __init__(self,obj):
56 def __init__(self,obj):
78 self.obj = obj
57 self.obj = obj
79
58
80 def __getattribute__(self,key):
59 def __getattribute__(self,key):
81 if key == '__doc__':
60 if key == '__doc__':
82 return DocTestSkip.ds_skip
61 return DocTestSkip.ds_skip
83 else:
62 else:
84 return getattr(object.__getattribute__(self,'obj'),key)
63 return getattr(object.__getattribute__(self,'obj'),key)
85
64
86 # Modified version of the one in the stdlib, that fixes a python bug (doctests
65 # Modified version of the one in the stdlib, that fixes a python bug (doctests
87 # not found in extension modules, http://bugs.python.org/issue3158)
66 # not found in extension modules, http://bugs.python.org/issue3158)
88 class DocTestFinder(doctest.DocTestFinder):
67 class DocTestFinder(doctest.DocTestFinder):
89
68
90 def _from_module(self, module, object):
69 def _from_module(self, module, object):
91 """
70 """
92 Return true if the given object is defined in the given
71 Return true if the given object is defined in the given
93 module.
72 module.
94 """
73 """
95 if module is None:
74 if module is None:
96 return True
75 return True
97 elif inspect.isfunction(object):
76 elif inspect.isfunction(object):
98 return module.__dict__ is object.__globals__
77 return module.__dict__ is object.__globals__
99 elif inspect.isbuiltin(object):
78 elif inspect.isbuiltin(object):
100 return module.__name__ == object.__module__
79 return module.__name__ == object.__module__
101 elif inspect.isclass(object):
80 elif inspect.isclass(object):
102 return module.__name__ == object.__module__
81 return module.__name__ == object.__module__
103 elif inspect.ismethod(object):
82 elif inspect.ismethod(object):
104 # This one may be a bug in cython that fails to correctly set the
83 # This one may be a bug in cython that fails to correctly set the
105 # __module__ attribute of methods, but since the same error is easy
84 # __module__ attribute of methods, but since the same error is easy
106 # to make by extension code writers, having this safety in place
85 # to make by extension code writers, having this safety in place
107 # isn't such a bad idea
86 # isn't such a bad idea
108 return module.__name__ == object.__self__.__class__.__module__
87 return module.__name__ == object.__self__.__class__.__module__
109 elif inspect.getmodule(object) is not None:
88 elif inspect.getmodule(object) is not None:
110 return module is inspect.getmodule(object)
89 return module is inspect.getmodule(object)
111 elif hasattr(object, '__module__'):
90 elif hasattr(object, '__module__'):
112 return module.__name__ == object.__module__
91 return module.__name__ == object.__module__
113 elif isinstance(object, property):
92 elif isinstance(object, property):
114 return True # [XX] no way not be sure.
93 return True # [XX] no way not be sure.
115 elif inspect.ismethoddescriptor(object):
94 elif inspect.ismethoddescriptor(object):
116 # Unbound PyQt signals reach this point in Python 3.4b3, and we want
95 # Unbound PyQt signals reach this point in Python 3.4b3, and we want
117 # to avoid throwing an error. See also http://bugs.python.org/issue3158
96 # to avoid throwing an error. See also http://bugs.python.org/issue3158
118 return False
97 return False
119 else:
98 else:
120 raise ValueError("object must be a class or function, got %r" % object)
99 raise ValueError("object must be a class or function, got %r" % object)
121
100
122 def _find(self, tests, obj, name, module, source_lines, globs, seen):
101 def _find(self, tests, obj, name, module, source_lines, globs, seen):
123 """
102 """
124 Find tests for the given object and any contained objects, and
103 Find tests for the given object and any contained objects, and
125 add them to `tests`.
104 add them to `tests`.
126 """
105 """
127 print('_find for:', obj, name, module) # dbg
106 print('_find for:', obj, name, module) # dbg
128 if bool(getattr(obj, "__skip_doctest__", False)):
107 if bool(getattr(obj, "__skip_doctest__", False)):
129 #print 'SKIPPING DOCTEST FOR:',obj # dbg
108 #print 'SKIPPING DOCTEST FOR:',obj # dbg
130 obj = DocTestSkip(obj)
109 obj = DocTestSkip(obj)
131
110
132 doctest.DocTestFinder._find(self,tests, obj, name, module,
111 doctest.DocTestFinder._find(self,tests, obj, name, module,
133 source_lines, globs, seen)
112 source_lines, globs, seen)
134
113
135 # Below we re-run pieces of the above method with manual modifications,
114 # Below we re-run pieces of the above method with manual modifications,
136 # because the original code is buggy and fails to correctly identify
115 # because the original code is buggy and fails to correctly identify
137 # doctests in extension modules.
116 # doctests in extension modules.
138
117
139 # Local shorthands
118 # Local shorthands
140 from inspect import isroutine, isclass
119 from inspect import isroutine, isclass
141
120
142 # Look for tests in a module's contained objects.
121 # Look for tests in a module's contained objects.
143 if inspect.ismodule(obj) and self._recurse:
122 if inspect.ismodule(obj) and self._recurse:
144 for valname, val in obj.__dict__.items():
123 for valname, val in obj.__dict__.items():
145 valname1 = '%s.%s' % (name, valname)
124 valname1 = '%s.%s' % (name, valname)
146 if ( (isroutine(val) or isclass(val))
125 if ( (isroutine(val) or isclass(val))
147 and self._from_module(module, val) ):
126 and self._from_module(module, val) ):
148
127
149 self._find(tests, val, valname1, module, source_lines,
128 self._find(tests, val, valname1, module, source_lines,
150 globs, seen)
129 globs, seen)
151
130
152 # Look for tests in a class's contained objects.
131 # Look for tests in a class's contained objects.
153 if inspect.isclass(obj) and self._recurse:
132 if inspect.isclass(obj) and self._recurse:
154 #print 'RECURSE into class:',obj # dbg
133 #print 'RECURSE into class:',obj # dbg
155 for valname, val in obj.__dict__.items():
134 for valname, val in obj.__dict__.items():
156 # Special handling for staticmethod/classmethod.
135 # Special handling for staticmethod/classmethod.
157 if isinstance(val, staticmethod):
136 if isinstance(val, staticmethod):
158 val = getattr(obj, valname)
137 val = getattr(obj, valname)
159 if isinstance(val, classmethod):
138 if isinstance(val, classmethod):
160 val = getattr(obj, valname).__func__
139 val = getattr(obj, valname).__func__
161
140
162 # Recurse to methods, properties, and nested classes.
141 # Recurse to methods, properties, and nested classes.
163 if ((inspect.isfunction(val) or inspect.isclass(val) or
142 if ((inspect.isfunction(val) or inspect.isclass(val) or
164 inspect.ismethod(val) or
143 inspect.ismethod(val) or
165 isinstance(val, property)) and
144 isinstance(val, property)) and
166 self._from_module(module, val)):
145 self._from_module(module, val)):
167 valname = '%s.%s' % (name, valname)
146 valname = '%s.%s' % (name, valname)
168 self._find(tests, val, valname, module, source_lines,
147 self._find(tests, val, valname, module, source_lines,
169 globs, seen)
148 globs, seen)
170
149
171
150
172 class IPDoctestOutputChecker(doctest.OutputChecker):
151 class IPDoctestOutputChecker(doctest.OutputChecker):
173 """Second-chance checker with support for random tests.
152 """Second-chance checker with support for random tests.
174
153
175 If the default comparison doesn't pass, this checker looks in the expected
154 If the default comparison doesn't pass, this checker looks in the expected
176 output string for flags that tell us to ignore the output.
155 output string for flags that tell us to ignore the output.
177 """
156 """
178
157
179 random_re = re.compile(r'#\s*random\s+')
158 random_re = re.compile(r'#\s*random\s+')
180
159
181 def check_output(self, want, got, optionflags):
160 def check_output(self, want, got, optionflags):
182 """Check output, accepting special markers embedded in the output.
161 """Check output, accepting special markers embedded in the output.
183
162
184 If the output didn't pass the default validation but the special string
163 If the output didn't pass the default validation but the special string
185 '#random' is included, we accept it."""
164 '#random' is included, we accept it."""
186
165
187 # Let the original tester verify first, in case people have valid tests
166 # Let the original tester verify first, in case people have valid tests
188 # that happen to have a comment saying '#random' embedded in.
167 # that happen to have a comment saying '#random' embedded in.
189 ret = doctest.OutputChecker.check_output(self, want, got,
168 ret = doctest.OutputChecker.check_output(self, want, got,
190 optionflags)
169 optionflags)
191 if not ret and self.random_re.search(want):
170 if not ret and self.random_re.search(want):
192 #print >> sys.stderr, 'RANDOM OK:',want # dbg
171 #print >> sys.stderr, 'RANDOM OK:',want # dbg
193 return True
172 return True
194
173
195 return ret
174 return ret
196
175
197
176
198 class DocTestCase(doctests.DocTestCase):
199 """Proxy for DocTestCase: provides an address() method that
200 returns the correct address for the doctest case. Otherwise
201 acts as a proxy to the test case. To provide hints for address(),
202 an obj may also be passed -- this will be used as the test object
203 for purposes of determining the test address, if it is provided.
204 """
205
206 # Note: this method was taken from numpy's nosetester module.
207
208 # Subclass nose.plugins.doctests.DocTestCase to work around a bug in
209 # its constructor that blocks non-default arguments from being passed
210 # down into doctest.DocTestCase
211
212 def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
213 checker=None, obj=None, result_var='_'):
214 self._result_var = result_var
215 doctests.DocTestCase.__init__(self, test,
216 optionflags=optionflags,
217 setUp=setUp, tearDown=tearDown,
218 checker=checker)
219 # Now we must actually copy the original constructor from the stdlib
220 # doctest class, because we can't call it directly and a bug in nose
221 # means it never gets passed the right arguments.
222
223 self._dt_optionflags = optionflags
224 self._dt_checker = checker
225 self._dt_test = test
226 self._dt_test_globs_ori = test.globs
227 self._dt_setUp = setUp
228 self._dt_tearDown = tearDown
229
230 # XXX - store this runner once in the object!
231 runner = IPDocTestRunner(optionflags=optionflags,
232 checker=checker, verbose=False)
233 self._dt_runner = runner
234
235
236 # Each doctest should remember the directory it was loaded from, so
237 # things like %run work without too many contortions
238 self._ori_dir = os.path.dirname(test.filename)
239
240 # Modified runTest from the default stdlib
241 def runTest(self):
242 test = self._dt_test
243 runner = self._dt_runner
244
245 old = sys.stdout
246 new = StringIO()
247 optionflags = self._dt_optionflags
248
249 if not (optionflags & REPORTING_FLAGS):
250 # The option flags don't include any reporting flags,
251 # so add the default reporting flags
252 optionflags |= _unittest_reportflags
253
254 try:
255 # Save our current directory and switch out to the one where the
256 # test was originally created, in case another doctest did a
257 # directory change. We'll restore this in the finally clause.
258 curdir = os.getcwd()
259 #print 'runTest in dir:', self._ori_dir # dbg
260 os.chdir(self._ori_dir)
261
262 runner.DIVIDER = "-"*70
263 failures, tries = runner.run(test,out=new.write,
264 clear_globs=False)
265 finally:
266 sys.stdout = old
267 os.chdir(curdir)
268
269 if failures:
270 raise self.failureException(self.format_failure(new.getvalue()))
271
272 def setUp(self):
273 """Modified test setup that syncs with ipython namespace"""
274 #print "setUp test", self._dt_test.examples # dbg
275 if isinstance(self._dt_test.examples[0], IPExample):
276 # for IPython examples *only*, we swap the globals with the ipython
277 # namespace, after updating it with the globals (which doctest
278 # fills with the necessary info from the module being tested).
279 self.user_ns_orig = {}
280 self.user_ns_orig.update(_ip.user_ns)
281 _ip.user_ns.update(self._dt_test.globs)
282 # We must remove the _ key in the namespace, so that Python's
283 # doctest code sets it naturally
284 _ip.user_ns.pop('_', None)
285 _ip.user_ns['__builtins__'] = builtin_mod
286 self._dt_test.globs = _ip.user_ns
287
288 super(DocTestCase, self).setUp()
289
290 def tearDown(self):
291
292 # Undo the test.globs reassignment we made, so that the parent class
293 # teardown doesn't destroy the ipython namespace
294 if isinstance(self._dt_test.examples[0], IPExample):
295 self._dt_test.globs = self._dt_test_globs_ori
296 _ip.user_ns.clear()
297 _ip.user_ns.update(self.user_ns_orig)
298
299 # XXX - fperez: I am not sure if this is truly a bug in nose 0.11, but
300 # it does look like one to me: its tearDown method tries to run
301 #
302 # delattr(builtin_mod, self._result_var)
303 #
304 # without checking that the attribute really is there; it implicitly
305 # assumes it should have been set via displayhook. But if the
306 # displayhook was never called, this doesn't necessarily happen. I
307 # haven't been able to find a little self-contained example outside of
308 # ipython that would show the problem so I can report it to the nose
309 # team, but it does happen a lot in our code.
310 #
311 # So here, we just protect as narrowly as possible by trapping an
312 # attribute error whose message would be the name of self._result_var,
313 # and letting any other error propagate.
314 try:
315 super(DocTestCase, self).tearDown()
316 except AttributeError as exc:
317 if exc.args[0] != self._result_var:
318 raise
319
320
321 # A simple subclassing of the original with a different class name, so we can
177 # A simple subclassing of the original with a different class name, so we can
322 # distinguish and treat differently IPython examples from pure python ones.
178 # distinguish and treat differently IPython examples from pure python ones.
323 class IPExample(doctest.Example): pass
179 class IPExample(doctest.Example): pass
324
180
325
181
326 class IPExternalExample(doctest.Example):
182 class IPExternalExample(doctest.Example):
327 """Doctest examples to be run in an external process."""
183 """Doctest examples to be run in an external process."""
328
184
329 def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
185 def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
330 options=None):
186 options=None):
331 # Parent constructor
187 # Parent constructor
332 doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options)
188 doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options)
333
189
334 # An EXTRA newline is needed to prevent pexpect hangs
190 # An EXTRA newline is needed to prevent pexpect hangs
335 self.source += '\n'
191 self.source += '\n'
336
192
337
193
338 class IPDocTestParser(doctest.DocTestParser):
194 class IPDocTestParser(doctest.DocTestParser):
339 """
195 """
340 A class used to parse strings containing doctest examples.
196 A class used to parse strings containing doctest examples.
341
197
342 Note: This is a version modified to properly recognize IPython input and
198 Note: This is a version modified to properly recognize IPython input and
343 convert any IPython examples into valid Python ones.
199 convert any IPython examples into valid Python ones.
344 """
200 """
345 # This regular expression is used to find doctest examples in a
201 # This regular expression is used to find doctest examples in a
346 # string. It defines three groups: `source` is the source code
202 # string. It defines three groups: `source` is the source code
347 # (including leading indentation and prompts); `indent` is the
203 # (including leading indentation and prompts); `indent` is the
348 # indentation of the first (PS1) line of the source code; and
204 # indentation of the first (PS1) line of the source code; and
349 # `want` is the expected output (including leading indentation).
205 # `want` is the expected output (including leading indentation).
350
206
351 # Classic Python prompts or default IPython ones
207 # Classic Python prompts or default IPython ones
352 _PS1_PY = r'>>>'
208 _PS1_PY = r'>>>'
353 _PS2_PY = r'\.\.\.'
209 _PS2_PY = r'\.\.\.'
354
210
355 _PS1_IP = r'In\ \[\d+\]:'
211 _PS1_IP = r'In\ \[\d+\]:'
356 _PS2_IP = r'\ \ \ \.\.\.+:'
212 _PS2_IP = r'\ \ \ \.\.\.+:'
357
213
358 _RE_TPL = r'''
214 _RE_TPL = r'''
359 # Source consists of a PS1 line followed by zero or more PS2 lines.
215 # Source consists of a PS1 line followed by zero or more PS2 lines.
360 (?P<source>
216 (?P<source>
361 (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
217 (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
362 (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
218 (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
363 \n? # a newline
219 \n? # a newline
364 # Want consists of any non-blank lines that do not start with PS1.
220 # Want consists of any non-blank lines that do not start with PS1.
365 (?P<want> (?:(?![ ]*$) # Not a blank line
221 (?P<want> (?:(?![ ]*$) # Not a blank line
366 (?![ ]*%s) # Not a line starting with PS1
222 (?![ ]*%s) # Not a line starting with PS1
367 (?![ ]*%s) # Not a line starting with PS2
223 (?![ ]*%s) # Not a line starting with PS2
368 .*$\n? # But any other line
224 .*$\n? # But any other line
369 )*)
225 )*)
370 '''
226 '''
371
227
372 _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
228 _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
373 re.MULTILINE | re.VERBOSE)
229 re.MULTILINE | re.VERBOSE)
374
230
375 _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
231 _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
376 re.MULTILINE | re.VERBOSE)
232 re.MULTILINE | re.VERBOSE)
377
233
378 # Mark a test as being fully random. In this case, we simply append the
234 # Mark a test as being fully random. In this case, we simply append the
379 # random marker ('#random') to each individual example's output. This way
235 # random marker ('#random') to each individual example's output. This way
380 # we don't need to modify any other code.
236 # we don't need to modify any other code.
381 _RANDOM_TEST = re.compile(r'#\s*all-random\s+')
237 _RANDOM_TEST = re.compile(r'#\s*all-random\s+')
382
238
383 # Mark tests to be executed in an external process - currently unsupported.
239 # Mark tests to be executed in an external process - currently unsupported.
384 _EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL')
240 _EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL')
385
241
386 def ip2py(self,source):
242 def ip2py(self,source):
387 """Convert input IPython source into valid Python."""
243 """Convert input IPython source into valid Python."""
388 block = _ip.input_transformer_manager.transform_cell(source)
244 block = _ip.input_transformer_manager.transform_cell(source)
389 if len(block.splitlines()) == 1:
245 if len(block.splitlines()) == 1:
390 return _ip.prefilter(block)
246 return _ip.prefilter(block)
391 else:
247 else:
392 return block
248 return block
393
249
394 def parse(self, string, name='<string>'):
250 def parse(self, string, name='<string>'):
395 """
251 """
396 Divide the given string into examples and intervening text,
252 Divide the given string into examples and intervening text,
397 and return them as a list of alternating Examples and strings.
253 and return them as a list of alternating Examples and strings.
398 Line numbers for the Examples are 0-based. The optional
254 Line numbers for the Examples are 0-based. The optional
399 argument `name` is a name identifying this string, and is only
255 argument `name` is a name identifying this string, and is only
400 used for error messages.
256 used for error messages.
401 """
257 """
402
258
403 #print 'Parse string:\n',string # dbg
259 #print 'Parse string:\n',string # dbg
404
260
405 string = string.expandtabs()
261 string = string.expandtabs()
406 # If all lines begin with the same indentation, then strip it.
262 # If all lines begin with the same indentation, then strip it.
407 min_indent = self._min_indent(string)
263 min_indent = self._min_indent(string)
408 if min_indent > 0:
264 if min_indent > 0:
409 string = '\n'.join([l[min_indent:] for l in string.split('\n')])
265 string = '\n'.join([l[min_indent:] for l in string.split('\n')])
410
266
411 output = []
267 output = []
412 charno, lineno = 0, 0
268 charno, lineno = 0, 0
413
269
414 # We make 'all random' tests by adding the '# random' mark to every
270 # We make 'all random' tests by adding the '# random' mark to every
415 # block of output in the test.
271 # block of output in the test.
416 if self._RANDOM_TEST.search(string):
272 if self._RANDOM_TEST.search(string):
417 random_marker = '\n# random'
273 random_marker = '\n# random'
418 else:
274 else:
419 random_marker = ''
275 random_marker = ''
420
276
421 # Whether to convert the input from ipython to python syntax
277 # Whether to convert the input from ipython to python syntax
422 ip2py = False
278 ip2py = False
423 # Find all doctest examples in the string. First, try them as Python
279 # Find all doctest examples in the string. First, try them as Python
424 # examples, then as IPython ones
280 # examples, then as IPython ones
425 terms = list(self._EXAMPLE_RE_PY.finditer(string))
281 terms = list(self._EXAMPLE_RE_PY.finditer(string))
426 if terms:
282 if terms:
427 # Normal Python example
283 # Normal Python example
428 #print '-'*70 # dbg
284 #print '-'*70 # dbg
429 #print 'PyExample, Source:\n',string # dbg
285 #print 'PyExample, Source:\n',string # dbg
430 #print '-'*70 # dbg
286 #print '-'*70 # dbg
431 Example = doctest.Example
287 Example = doctest.Example
432 else:
288 else:
433 # It's an ipython example. Note that IPExamples are run
289 # It's an ipython example. Note that IPExamples are run
434 # in-process, so their syntax must be turned into valid python.
290 # in-process, so their syntax must be turned into valid python.
435 # IPExternalExamples are run out-of-process (via pexpect) so they
291 # IPExternalExamples are run out-of-process (via pexpect) so they
436 # don't need any filtering (a real ipython will be executing them).
292 # don't need any filtering (a real ipython will be executing them).
437 terms = list(self._EXAMPLE_RE_IP.finditer(string))
293 terms = list(self._EXAMPLE_RE_IP.finditer(string))
438 if self._EXTERNAL_IP.search(string):
294 if self._EXTERNAL_IP.search(string):
439 #print '-'*70 # dbg
295 #print '-'*70 # dbg
440 #print 'IPExternalExample, Source:\n',string # dbg
296 #print 'IPExternalExample, Source:\n',string # dbg
441 #print '-'*70 # dbg
297 #print '-'*70 # dbg
442 Example = IPExternalExample
298 Example = IPExternalExample
443 else:
299 else:
444 #print '-'*70 # dbg
300 #print '-'*70 # dbg
445 #print 'IPExample, Source:\n',string # dbg
301 #print 'IPExample, Source:\n',string # dbg
446 #print '-'*70 # dbg
302 #print '-'*70 # dbg
447 Example = IPExample
303 Example = IPExample
448 ip2py = True
304 ip2py = True
449
305
450 for m in terms:
306 for m in terms:
451 # Add the pre-example text to `output`.
307 # Add the pre-example text to `output`.
452 output.append(string[charno:m.start()])
308 output.append(string[charno:m.start()])
453 # Update lineno (lines before this example)
309 # Update lineno (lines before this example)
454 lineno += string.count('\n', charno, m.start())
310 lineno += string.count('\n', charno, m.start())
455 # Extract info from the regexp match.
311 # Extract info from the regexp match.
456 (source, options, want, exc_msg) = \
312 (source, options, want, exc_msg) = \
457 self._parse_example(m, name, lineno,ip2py)
313 self._parse_example(m, name, lineno,ip2py)
458
314
459 # Append the random-output marker (it defaults to empty in most
315 # Append the random-output marker (it defaults to empty in most
460 # cases, it's only non-empty for 'all-random' tests):
316 # cases, it's only non-empty for 'all-random' tests):
461 want += random_marker
317 want += random_marker
462
318
463 if Example is IPExternalExample:
319 if Example is IPExternalExample:
464 options[doctest.NORMALIZE_WHITESPACE] = True
320 options[doctest.NORMALIZE_WHITESPACE] = True
465 want += '\n'
321 want += '\n'
466
322
467 # Create an Example, and add it to the list.
323 # Create an Example, and add it to the list.
468 if not self._IS_BLANK_OR_COMMENT(source):
324 if not self._IS_BLANK_OR_COMMENT(source):
469 output.append(Example(source, want, exc_msg,
325 output.append(Example(source, want, exc_msg,
470 lineno=lineno,
326 lineno=lineno,
471 indent=min_indent+len(m.group('indent')),
327 indent=min_indent+len(m.group('indent')),
472 options=options))
328 options=options))
473 # Update lineno (lines inside this example)
329 # Update lineno (lines inside this example)
474 lineno += string.count('\n', m.start(), m.end())
330 lineno += string.count('\n', m.start(), m.end())
475 # Update charno.
331 # Update charno.
476 charno = m.end()
332 charno = m.end()
477 # Add any remaining post-example text to `output`.
333 # Add any remaining post-example text to `output`.
478 output.append(string[charno:])
334 output.append(string[charno:])
479 return output
335 return output
480
336
481 def _parse_example(self, m, name, lineno,ip2py=False):
337 def _parse_example(self, m, name, lineno,ip2py=False):
482 """
338 """
483 Given a regular expression match from `_EXAMPLE_RE` (`m`),
339 Given a regular expression match from `_EXAMPLE_RE` (`m`),
484 return a pair `(source, want)`, where `source` is the matched
340 return a pair `(source, want)`, where `source` is the matched
485 example's source code (with prompts and indentation stripped);
341 example's source code (with prompts and indentation stripped);
486 and `want` is the example's expected output (with indentation
342 and `want` is the example's expected output (with indentation
487 stripped).
343 stripped).
488
344
489 `name` is the string's name, and `lineno` is the line number
345 `name` is the string's name, and `lineno` is the line number
490 where the example starts; both are used for error messages.
346 where the example starts; both are used for error messages.
491
347
492 Optional:
348 Optional:
493 `ip2py`: if true, filter the input via IPython to convert the syntax
349 `ip2py`: if true, filter the input via IPython to convert the syntax
494 into valid python.
350 into valid python.
495 """
351 """
496
352
497 # Get the example's indentation level.
353 # Get the example's indentation level.
498 indent = len(m.group('indent'))
354 indent = len(m.group('indent'))
499
355
500 # Divide source into lines; check that they're properly
356 # Divide source into lines; check that they're properly
501 # indented; and then strip their indentation & prompts.
357 # indented; and then strip their indentation & prompts.
502 source_lines = m.group('source').split('\n')
358 source_lines = m.group('source').split('\n')
503
359
504 # We're using variable-length input prompts
360 # We're using variable-length input prompts
505 ps1 = m.group('ps1')
361 ps1 = m.group('ps1')
506 ps2 = m.group('ps2')
362 ps2 = m.group('ps2')
507 ps1_len = len(ps1)
363 ps1_len = len(ps1)
508
364
509 self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
365 self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
510 if ps2:
366 if ps2:
511 self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
367 self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
512
368
513 source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
369 source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
514
370
515 if ip2py:
371 if ip2py:
516 # Convert source input from IPython into valid Python syntax
372 # Convert source input from IPython into valid Python syntax
517 source = self.ip2py(source)
373 source = self.ip2py(source)
518
374
519 # Divide want into lines; check that it's properly indented; and
375 # Divide want into lines; check that it's properly indented; and
520 # then strip the indentation. Spaces before the last newline should
376 # then strip the indentation. Spaces before the last newline should
521 # be preserved, so plain rstrip() isn't good enough.
377 # be preserved, so plain rstrip() isn't good enough.
522 want = m.group('want')
378 want = m.group('want')
523 want_lines = want.split('\n')
379 want_lines = want.split('\n')
524 if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
380 if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
525 del want_lines[-1] # forget final newline & spaces after it
381 del want_lines[-1] # forget final newline & spaces after it
526 self._check_prefix(want_lines, ' '*indent, name,
382 self._check_prefix(want_lines, ' '*indent, name,
527 lineno + len(source_lines))
383 lineno + len(source_lines))
528
384
529 # Remove ipython output prompt that might be present in the first line
385 # Remove ipython output prompt that might be present in the first line
530 want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
386 want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
531
387
532 want = '\n'.join([wl[indent:] for wl in want_lines])
388 want = '\n'.join([wl[indent:] for wl in want_lines])
533
389
534 # If `want` contains a traceback message, then extract it.
390 # If `want` contains a traceback message, then extract it.
535 m = self._EXCEPTION_RE.match(want)
391 m = self._EXCEPTION_RE.match(want)
536 if m:
392 if m:
537 exc_msg = m.group('msg')
393 exc_msg = m.group('msg')
538 else:
394 else:
539 exc_msg = None
395 exc_msg = None
540
396
541 # Extract options from the source.
397 # Extract options from the source.
542 options = self._find_options(source, name, lineno)
398 options = self._find_options(source, name, lineno)
543
399
544 return source, options, want, exc_msg
400 return source, options, want, exc_msg
545
401
546 def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
402 def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
547 """
403 """
548 Given the lines of a source string (including prompts and
404 Given the lines of a source string (including prompts and
549 leading indentation), check to make sure that every prompt is
405 leading indentation), check to make sure that every prompt is
550 followed by a space character. If any line is not followed by
406 followed by a space character. If any line is not followed by
551 a space character, then raise ValueError.
407 a space character, then raise ValueError.
552
408
553 Note: IPython-modified version which takes the input prompt length as a
409 Note: IPython-modified version which takes the input prompt length as a
554 parameter, so that prompts of variable length can be dealt with.
410 parameter, so that prompts of variable length can be dealt with.
555 """
411 """
556 space_idx = indent+ps1_len
412 space_idx = indent+ps1_len
557 min_len = space_idx+1
413 min_len = space_idx+1
558 for i, line in enumerate(lines):
414 for i, line in enumerate(lines):
559 if len(line) >= min_len and line[space_idx] != ' ':
415 if len(line) >= min_len and line[space_idx] != ' ':
560 raise ValueError('line %r of the docstring for %s '
416 raise ValueError('line %r of the docstring for %s '
561 'lacks blank after %s: %r' %
417 'lacks blank after %s: %r' %
562 (lineno+i+1, name,
418 (lineno+i+1, name,
563 line[indent:space_idx], line))
419 line[indent:space_idx], line))
564
420
565
421
566 SKIP = doctest.register_optionflag('SKIP')
422 SKIP = doctest.register_optionflag('SKIP')
567
423
568
424
569 class IPDocTestRunner(doctest.DocTestRunner,object):
425 class IPDocTestRunner(doctest.DocTestRunner,object):
570 """Test runner that synchronizes the IPython namespace with test globals.
426 """Test runner that synchronizes the IPython namespace with test globals.
571 """
427 """
572
428
573 def run(self, test, compileflags=None, out=None, clear_globs=True):
429 def run(self, test, compileflags=None, out=None, clear_globs=True):
574
430
575 # Hack: ipython needs access to the execution context of the example,
431 # Hack: ipython needs access to the execution context of the example,
576 # so that it can propagate user variables loaded by %run into
432 # so that it can propagate user variables loaded by %run into
577 # test.globs. We put them here into our modified %run as a function
433 # test.globs. We put them here into our modified %run as a function
578 # attribute. Our new %run will then only make the namespace update
434 # attribute. Our new %run will then only make the namespace update
579 # when called (rather than unconditionally updating test.globs here
435 # when called (rather than unconditionally updating test.globs here
580 # for all examples, most of which won't be calling %run anyway).
436 # for all examples, most of which won't be calling %run anyway).
581 #_ip._ipdoctest_test_globs = test.globs
437 #_ip._ipdoctest_test_globs = test.globs
582 #_ip._ipdoctest_test_filename = test.filename
438 #_ip._ipdoctest_test_filename = test.filename
583
439
584 test.globs.update(_ip.user_ns)
440 test.globs.update(_ip.user_ns)
585
441
586 # Override terminal size to standardise traceback format
442 # Override terminal size to standardise traceback format
587 with modified_env({'COLUMNS': '80', 'LINES': '24'}):
443 with modified_env({'COLUMNS': '80', 'LINES': '24'}):
588 return super(IPDocTestRunner,self).run(test,
444 return super(IPDocTestRunner,self).run(test,
589 compileflags,out,clear_globs)
445 compileflags,out,clear_globs)
590
446
591
447
592 class DocFileCase(doctest.DocFileCase):
448 class DocFileCase(doctest.DocFileCase):
593 """Overrides to provide filename
449 """Overrides to provide filename
594 """
450 """
595 def address(self):
451 def address(self):
596 return (self._dt_test.filename, None, None)
452 return (self._dt_test.filename, None, None)
597
598
599 class ExtensionDoctest(doctests.Doctest):
600 """Nose Plugin that supports doctests in extension modules.
601 """
602 name = 'extdoctest' # call nosetests with --with-extdoctest
603 enabled = True
604
605 def options(self, parser, env=os.environ):
606 Plugin.options(self, parser, env)
607 parser.add_option('--doctest-tests', action='store_true',
608 dest='doctest_tests',
609 default=env.get('NOSE_DOCTEST_TESTS',True),
610 help="Also look for doctests in test modules. "
611 "Note that classes, methods and functions should "
612 "have either doctests or non-doctest tests, "
613 "not both. [NOSE_DOCTEST_TESTS]")
614 parser.add_option('--doctest-extension', action="append",
615 dest="doctestExtension",
616 help="Also look for doctests in files with "
617 "this extension [NOSE_DOCTEST_EXTENSION]")
618 # Set the default as a list, if given in env; otherwise
619 # an additional value set on the command line will cause
620 # an error.
621 env_setting = env.get('NOSE_DOCTEST_EXTENSION')
622 if env_setting is not None:
623 parser.set_defaults(doctestExtension=tolist(env_setting))
624
625
626 def configure(self, options, config):
627 Plugin.configure(self, options, config)
628 # Pull standard doctest plugin out of config; we will do doctesting
629 config.plugins.plugins = [p for p in config.plugins.plugins
630 if p.name != 'doctest']
631 self.doctest_tests = options.doctest_tests
632 self.extension = tolist(options.doctestExtension)
633
634 self.parser = doctest.DocTestParser()
635 self.finder = DocTestFinder()
636 self.checker = IPDoctestOutputChecker()
637 self.globs = None
638 self.extraglobs = None
639
640
641 def loadTestsFromExtensionModule(self,filename):
642 bpath,mod = os.path.split(filename)
643 modname = os.path.splitext(mod)[0]
644 try:
645 sys.path.append(bpath)
646 module = import_module(modname)
647 tests = list(self.loadTestsFromModule(module))
648 finally:
649 sys.path.pop()
650 return tests
651
652 # NOTE: the method below is almost a copy of the original one in nose, with
653 # a few modifications to control output checking.
654
655 def loadTestsFromModule(self, module):
656 #print '*** ipdoctest - lTM',module # dbg
657
658 if not self.matches(module.__name__):
659 log.debug("Doctest doesn't want module %s", module)
660 return
661
662 tests = self.finder.find(module,globs=self.globs,
663 extraglobs=self.extraglobs)
664 if not tests:
665 return
666
667 # always use whitespace and ellipsis options
668 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
669
670 tests.sort()
671 module_file = module.__file__
672 if module_file[-4:] in ('.pyc', '.pyo'):
673 module_file = module_file[:-1]
674 for test in tests:
675 if not test.examples:
676 continue
677 if not test.filename:
678 test.filename = module_file
679
680 yield DocTestCase(test,
681 optionflags=optionflags,
682 checker=self.checker)
683
684
685 def loadTestsFromFile(self, filename):
686 #print "ipdoctest - from file", filename # dbg
687 if is_extension_module(filename):
688 for t in self.loadTestsFromExtensionModule(filename):
689 yield t
690 else:
691 if self.extension and anyp(filename.endswith, self.extension):
692 name = PurePath(filename).name
693 doc = Path(filename).read_text()
694 test = self.parser.get_doctest(
695 doc, globs={'__file__': filename}, name=name,
696 filename=filename, lineno=0)
697 if test.examples:
698 #print 'FileCase:',test.examples # dbg
699 yield DocFileCase(test)
700 else:
701 yield False # no tests to load
702
703
704 class IPythonDoctest(ExtensionDoctest):
705 """Nose Plugin that supports doctests in extension modules.
706 """
707 name = 'ipdoctest' # call nosetests with --with-ipdoctest
708 enabled = True
709
710 def makeTest(self, obj, parent):
711 """Look for doctests in the given object, which will be a
712 function, method or class.
713 """
714 #print 'Plugin analyzing:', obj, parent # dbg
715 # always use whitespace and ellipsis options
716 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
717
718 doctests = self.finder.find(obj, module=getmodule(parent))
719 if doctests:
720 for test in doctests:
721 if len(test.examples) == 0:
722 continue
723
724 yield DocTestCase(test, obj=obj,
725 optionflags=optionflags,
726 checker=self.checker)
727
728 def options(self, parser, env=os.environ):
729 #print "Options for nose plugin:", self.name # dbg
730 Plugin.options(self, parser, env)
731 parser.add_option('--ipdoctest-tests', action='store_true',
732 dest='ipdoctest_tests',
733 default=env.get('NOSE_IPDOCTEST_TESTS',True),
734 help="Also look for doctests in test modules. "
735 "Note that classes, methods and functions should "
736 "have either doctests or non-doctest tests, "
737 "not both. [NOSE_IPDOCTEST_TESTS]")
738 parser.add_option('--ipdoctest-extension', action="append",
739 dest="ipdoctest_extension",
740 help="Also look for doctests in files with "
741 "this extension [NOSE_IPDOCTEST_EXTENSION]")
742 # Set the default as a list, if given in env; otherwise
743 # an additional value set on the command line will cause
744 # an error.
745 env_setting = env.get('NOSE_IPDOCTEST_EXTENSION')
746 if env_setting is not None:
747 parser.set_defaults(ipdoctest_extension=tolist(env_setting))
748
749 def configure(self, options, config):
750 #print "Configuring nose plugin:", self.name # dbg
751 Plugin.configure(self, options, config)
752 # Pull standard doctest plugin out of config; we will do doctesting
753 config.plugins.plugins = [p for p in config.plugins.plugins
754 if p.name != 'doctest']
755 self.doctest_tests = options.ipdoctest_tests
756 self.extension = tolist(options.ipdoctest_extension)
757
758 self.parser = IPDocTestParser()
759 self.finder = DocTestFinder(parser=self.parser)
760 self.checker = IPDoctestOutputChecker()
761 self.globs = None
762 self.extraglobs = None
@@ -1,493 +1,502 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for IPython.utils.path.py"""
2 """Tests for IPython.utils.path.py"""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 import os
7 import os
8 import shutil
8 import shutil
9 import sys
9 import sys
10 import tempfile
10 import tempfile
11 import unittest
11 import unittest
12 from contextlib import contextmanager
12 from contextlib import contextmanager
13 from unittest.mock import patch
13 from unittest.mock import patch
14 from os.path import join, abspath
14 from os.path import join, abspath
15 from imp import reload
15 from imp import reload
16
16
17 from nose import SkipTest, with_setup
18 import pytest
17 import pytest
19
18
20 import IPython
19 import IPython
21 from IPython import paths
20 from IPython import paths
22 from IPython.testing import decorators as dec
21 from IPython.testing import decorators as dec
23 from IPython.testing.decorators import (skip_if_not_win32, skip_win32,
22 from IPython.testing.decorators import (skip_if_not_win32, skip_win32,
24 onlyif_unicode_paths,
23 onlyif_unicode_paths,
25 skip_win32_py38,)
24 skip_win32_py38,)
26 from IPython.testing.tools import make_tempfile
25 from IPython.testing.tools import make_tempfile
27 from IPython.utils import path
26 from IPython.utils import path
28 from IPython.utils.tempdir import TemporaryDirectory
27 from IPython.utils.tempdir import TemporaryDirectory
29
28
30
29
31 # Platform-dependent imports
30 # Platform-dependent imports
32 try:
31 try:
33 import winreg as wreg
32 import winreg as wreg
34 except ImportError:
33 except ImportError:
35 #Fake _winreg module on non-windows platforms
34 #Fake _winreg module on non-windows platforms
36 import types
35 import types
37 wr_name = "winreg"
36 wr_name = "winreg"
38 sys.modules[wr_name] = types.ModuleType(wr_name)
37 sys.modules[wr_name] = types.ModuleType(wr_name)
39 try:
38 try:
40 import winreg as wreg
39 import winreg as wreg
41 except ImportError:
40 except ImportError:
42 import _winreg as wreg
41 import _winreg as wreg
43 #Add entries that needs to be stubbed by the testing code
42 #Add entries that needs to be stubbed by the testing code
44 (wreg.OpenKey, wreg.QueryValueEx,) = (None, None)
43 (wreg.OpenKey, wreg.QueryValueEx,) = (None, None)
45
44
46 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
47 # Globals
46 # Globals
48 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
49 env = os.environ
48 env = os.environ
50 TMP_TEST_DIR = tempfile.mkdtemp()
49 TMP_TEST_DIR = tempfile.mkdtemp()
51 HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir")
50 HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir")
52 #
51 #
53 # Setup/teardown functions/decorators
52 # Setup/teardown functions/decorators
54 #
53 #
55
54
56 def setup_module():
55 def setup_module():
57 """Setup testenvironment for the module:
56 """Setup testenvironment for the module:
58
57
59 - Adds dummy home dir tree
58 - Adds dummy home dir tree
60 """
59 """
61 # Do not mask exceptions here. In particular, catching WindowsError is a
60 # Do not mask exceptions here. In particular, catching WindowsError is a
62 # problem because that exception is only defined on Windows...
61 # problem because that exception is only defined on Windows...
63 os.makedirs(os.path.join(HOME_TEST_DIR, 'ipython'))
62 os.makedirs(os.path.join(HOME_TEST_DIR, 'ipython'))
64
63
65
64
66 def teardown_module():
65 def teardown_module():
67 """Teardown testenvironment for the module:
66 """Teardown testenvironment for the module:
68
67
69 - Remove dummy home dir tree
68 - Remove dummy home dir tree
70 """
69 """
71 # Note: we remove the parent test dir, which is the root of all test
70 # Note: we remove the parent test dir, which is the root of all test
72 # subdirs we may have created. Use shutil instead of os.removedirs, so
71 # subdirs we may have created. Use shutil instead of os.removedirs, so
73 # that non-empty directories are all recursively removed.
72 # that non-empty directories are all recursively removed.
74 shutil.rmtree(TMP_TEST_DIR)
73 shutil.rmtree(TMP_TEST_DIR)
75
74
76
75
77 def setup_environment():
76 def setup_environment():
78 """Setup testenvironment for some functions that are tested
77 """Setup testenvironment for some functions that are tested
79 in this module. In particular this functions stores attributes
78 in this module. In particular this functions stores attributes
80 and other things that we need to stub in some test functions.
79 and other things that we need to stub in some test functions.
81 This needs to be done on a function level and not module level because
80 This needs to be done on a function level and not module level because
82 each testfunction needs a pristine environment.
81 each testfunction needs a pristine environment.
83 """
82 """
84 global oldstuff, platformstuff
83 global oldstuff, platformstuff
85 oldstuff = (env.copy(), os.name, sys.platform, path.get_home_dir, IPython.__file__, os.getcwd())
84 oldstuff = (env.copy(), os.name, sys.platform, path.get_home_dir, IPython.__file__, os.getcwd())
86
85
87 def teardown_environment():
86 def teardown_environment():
88 """Restore things that were remembered by the setup_environment function
87 """Restore things that were remembered by the setup_environment function
89 """
88 """
90 (oldenv, os.name, sys.platform, path.get_home_dir, IPython.__file__, old_wd) = oldstuff
89 (oldenv, os.name, sys.platform, path.get_home_dir, IPython.__file__, old_wd) = oldstuff
91 os.chdir(old_wd)
90 os.chdir(old_wd)
92 reload(path)
91 reload(path)
93
92
94 for key in list(env):
93 for key in list(env):
95 if key not in oldenv:
94 if key not in oldenv:
96 del env[key]
95 del env[key]
97 env.update(oldenv)
96 env.update(oldenv)
98 if hasattr(sys, 'frozen'):
97 if hasattr(sys, 'frozen'):
99 del sys.frozen
98 del sys.frozen
100
99
100
101 # Build decorator that uses the setup_environment/setup_environment
101 # Build decorator that uses the setup_environment/setup_environment
102 with_environment = with_setup(setup_environment, teardown_environment)
102 @pytest.fixture
103 def environment():
104 setup_environment()
105 yield
106 teardown_environment()
107
108
109 with_environment = pytest.mark.usefixtures("environment")
110
103
111
104 @skip_if_not_win32
112 @skip_if_not_win32
105 @with_environment
113 @with_environment
106 def test_get_home_dir_1():
114 def test_get_home_dir_1():
107 """Testcase for py2exe logic, un-compressed lib
115 """Testcase for py2exe logic, un-compressed lib
108 """
116 """
109 unfrozen = path.get_home_dir()
117 unfrozen = path.get_home_dir()
110 sys.frozen = True
118 sys.frozen = True
111
119
112 #fake filename for IPython.__init__
120 #fake filename for IPython.__init__
113 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py"))
121 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py"))
114
122
115 home_dir = path.get_home_dir()
123 home_dir = path.get_home_dir()
116 assert home_dir == unfrozen
124 assert home_dir == unfrozen
117
125
118
126
119 @skip_if_not_win32
127 @skip_if_not_win32
120 @with_environment
128 @with_environment
121 def test_get_home_dir_2():
129 def test_get_home_dir_2():
122 """Testcase for py2exe logic, compressed lib
130 """Testcase for py2exe logic, compressed lib
123 """
131 """
124 unfrozen = path.get_home_dir()
132 unfrozen = path.get_home_dir()
125 sys.frozen = True
133 sys.frozen = True
126 #fake filename for IPython.__init__
134 #fake filename for IPython.__init__
127 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower()
135 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower()
128
136
129 home_dir = path.get_home_dir(True)
137 home_dir = path.get_home_dir(True)
130 assert home_dir == unfrozen
138 assert home_dir == unfrozen
131
139
132
140
133 @skip_win32_py38
141 @skip_win32_py38
134 @with_environment
142 @with_environment
135 def test_get_home_dir_3():
143 def test_get_home_dir_3():
136 """get_home_dir() uses $HOME if set"""
144 """get_home_dir() uses $HOME if set"""
137 env["HOME"] = HOME_TEST_DIR
145 env["HOME"] = HOME_TEST_DIR
138 home_dir = path.get_home_dir(True)
146 home_dir = path.get_home_dir(True)
139 # get_home_dir expands symlinks
147 # get_home_dir expands symlinks
140 assert home_dir == os.path.realpath(env["HOME"])
148 assert home_dir == os.path.realpath(env["HOME"])
141
149
142
150
143 @with_environment
151 @with_environment
144 def test_get_home_dir_4():
152 def test_get_home_dir_4():
145 """get_home_dir() still works if $HOME is not set"""
153 """get_home_dir() still works if $HOME is not set"""
146
154
147 if 'HOME' in env: del env['HOME']
155 if 'HOME' in env: del env['HOME']
148 # this should still succeed, but we don't care what the answer is
156 # this should still succeed, but we don't care what the answer is
149 home = path.get_home_dir(False)
157 home = path.get_home_dir(False)
150
158
151 @skip_win32_py38
159 @skip_win32_py38
152 @with_environment
160 @with_environment
153 def test_get_home_dir_5():
161 def test_get_home_dir_5():
154 """raise HomeDirError if $HOME is specified, but not a writable dir"""
162 """raise HomeDirError if $HOME is specified, but not a writable dir"""
155 env['HOME'] = abspath(HOME_TEST_DIR+'garbage')
163 env['HOME'] = abspath(HOME_TEST_DIR+'garbage')
156 # set os.name = posix, to prevent My Documents fallback on Windows
164 # set os.name = posix, to prevent My Documents fallback on Windows
157 os.name = 'posix'
165 os.name = 'posix'
158 pytest.raises(path.HomeDirError, path.get_home_dir, True)
166 pytest.raises(path.HomeDirError, path.get_home_dir, True)
159
167
160 # Should we stub wreg fully so we can run the test on all platforms?
168 # Should we stub wreg fully so we can run the test on all platforms?
161 @skip_if_not_win32
169 @skip_if_not_win32
162 @with_environment
170 @with_environment
163 def test_get_home_dir_8():
171 def test_get_home_dir_8():
164 """Using registry hack for 'My Documents', os=='nt'
172 """Using registry hack for 'My Documents', os=='nt'
165
173
166 HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing.
174 HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing.
167 """
175 """
168 os.name = 'nt'
176 os.name = 'nt'
169 # Remove from stub environment all keys that may be set
177 # Remove from stub environment all keys that may be set
170 for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']:
178 for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']:
171 env.pop(key, None)
179 env.pop(key, None)
172
180
173 class key:
181 class key:
174 def __enter__(self):
182 def __enter__(self):
175 pass
183 pass
176 def Close(self):
184 def Close(self):
177 pass
185 pass
178 def __exit__(*args, **kwargs):
186 def __exit__(*args, **kwargs):
179 pass
187 pass
180
188
181 with patch.object(wreg, 'OpenKey', return_value=key()), \
189 with patch.object(wreg, 'OpenKey', return_value=key()), \
182 patch.object(wreg, 'QueryValueEx', return_value=[abspath(HOME_TEST_DIR)]):
190 patch.object(wreg, 'QueryValueEx', return_value=[abspath(HOME_TEST_DIR)]):
183 home_dir = path.get_home_dir()
191 home_dir = path.get_home_dir()
184 assert home_dir == abspath(HOME_TEST_DIR)
192 assert home_dir == abspath(HOME_TEST_DIR)
185
193
186 @with_environment
194 @with_environment
187 def test_get_xdg_dir_0():
195 def test_get_xdg_dir_0():
188 """test_get_xdg_dir_0, check xdg_dir"""
196 """test_get_xdg_dir_0, check xdg_dir"""
189 reload(path)
197 reload(path)
190 path._writable_dir = lambda path: True
198 path._writable_dir = lambda path: True
191 path.get_home_dir = lambda : 'somewhere'
199 path.get_home_dir = lambda : 'somewhere'
192 os.name = "posix"
200 os.name = "posix"
193 sys.platform = "linux2"
201 sys.platform = "linux2"
194 env.pop('IPYTHON_DIR', None)
202 env.pop('IPYTHON_DIR', None)
195 env.pop('IPYTHONDIR', None)
203 env.pop('IPYTHONDIR', None)
196 env.pop('XDG_CONFIG_HOME', None)
204 env.pop('XDG_CONFIG_HOME', None)
197
205
198 assert path.get_xdg_dir() == os.path.join("somewhere", ".config")
206 assert path.get_xdg_dir() == os.path.join("somewhere", ".config")
199
207
200
208
201 @with_environment
209 @with_environment
202 def test_get_xdg_dir_1():
210 def test_get_xdg_dir_1():
203 """test_get_xdg_dir_1, check nonexistent xdg_dir"""
211 """test_get_xdg_dir_1, check nonexistent xdg_dir"""
204 reload(path)
212 reload(path)
205 path.get_home_dir = lambda : HOME_TEST_DIR
213 path.get_home_dir = lambda : HOME_TEST_DIR
206 os.name = "posix"
214 os.name = "posix"
207 sys.platform = "linux2"
215 sys.platform = "linux2"
208 env.pop('IPYTHON_DIR', None)
216 env.pop('IPYTHON_DIR', None)
209 env.pop('IPYTHONDIR', None)
217 env.pop('IPYTHONDIR', None)
210 env.pop('XDG_CONFIG_HOME', None)
218 env.pop('XDG_CONFIG_HOME', None)
211 assert path.get_xdg_dir() is None
219 assert path.get_xdg_dir() is None
212
220
213 @with_environment
221 @with_environment
214 def test_get_xdg_dir_2():
222 def test_get_xdg_dir_2():
215 """test_get_xdg_dir_2, check xdg_dir default to ~/.config"""
223 """test_get_xdg_dir_2, check xdg_dir default to ~/.config"""
216 reload(path)
224 reload(path)
217 path.get_home_dir = lambda : HOME_TEST_DIR
225 path.get_home_dir = lambda : HOME_TEST_DIR
218 os.name = "posix"
226 os.name = "posix"
219 sys.platform = "linux2"
227 sys.platform = "linux2"
220 env.pop('IPYTHON_DIR', None)
228 env.pop('IPYTHON_DIR', None)
221 env.pop('IPYTHONDIR', None)
229 env.pop('IPYTHONDIR', None)
222 env.pop('XDG_CONFIG_HOME', None)
230 env.pop('XDG_CONFIG_HOME', None)
223 cfgdir=os.path.join(path.get_home_dir(), '.config')
231 cfgdir=os.path.join(path.get_home_dir(), '.config')
224 if not os.path.exists(cfgdir):
232 if not os.path.exists(cfgdir):
225 os.makedirs(cfgdir)
233 os.makedirs(cfgdir)
226
234
227 assert path.get_xdg_dir() == cfgdir
235 assert path.get_xdg_dir() == cfgdir
228
236
229 @with_environment
237 @with_environment
230 def test_get_xdg_dir_3():
238 def test_get_xdg_dir_3():
231 """test_get_xdg_dir_3, check xdg_dir not used on OS X"""
239 """test_get_xdg_dir_3, check xdg_dir not used on OS X"""
232 reload(path)
240 reload(path)
233 path.get_home_dir = lambda : HOME_TEST_DIR
241 path.get_home_dir = lambda : HOME_TEST_DIR
234 os.name = "posix"
242 os.name = "posix"
235 sys.platform = "darwin"
243 sys.platform = "darwin"
236 env.pop('IPYTHON_DIR', None)
244 env.pop('IPYTHON_DIR', None)
237 env.pop('IPYTHONDIR', None)
245 env.pop('IPYTHONDIR', None)
238 env.pop('XDG_CONFIG_HOME', None)
246 env.pop('XDG_CONFIG_HOME', None)
239 cfgdir=os.path.join(path.get_home_dir(), '.config')
247 cfgdir=os.path.join(path.get_home_dir(), '.config')
240 os.makedirs(cfgdir, exist_ok=True)
248 os.makedirs(cfgdir, exist_ok=True)
241
249
242 assert path.get_xdg_dir() is None
250 assert path.get_xdg_dir() is None
243
251
244 def test_filefind():
252 def test_filefind():
245 """Various tests for filefind"""
253 """Various tests for filefind"""
246 f = tempfile.NamedTemporaryFile()
254 f = tempfile.NamedTemporaryFile()
247 # print 'fname:',f.name
255 # print 'fname:',f.name
248 alt_dirs = paths.get_ipython_dir()
256 alt_dirs = paths.get_ipython_dir()
249 t = path.filefind(f.name, alt_dirs)
257 t = path.filefind(f.name, alt_dirs)
250 # print 'found:',t
258 # print 'found:',t
251
259
252
260
253 @dec.skip_if_not_win32
261 @dec.skip_if_not_win32
254 def test_get_long_path_name_win32():
262 def test_get_long_path_name_win32():
255 with TemporaryDirectory() as tmpdir:
263 with TemporaryDirectory() as tmpdir:
256
264
257 # Make a long path. Expands the path of tmpdir prematurely as it may already have a long
265 # Make a long path. Expands the path of tmpdir prematurely as it may already have a long
258 # path component, so ensure we include the long form of it
266 # path component, so ensure we include the long form of it
259 long_path = os.path.join(path.get_long_path_name(tmpdir), 'this is my long path name')
267 long_path = os.path.join(path.get_long_path_name(tmpdir), 'this is my long path name')
260 os.makedirs(long_path)
268 os.makedirs(long_path)
261
269
262 # Test to see if the short path evaluates correctly.
270 # Test to see if the short path evaluates correctly.
263 short_path = os.path.join(tmpdir, 'THISIS~1')
271 short_path = os.path.join(tmpdir, 'THISIS~1')
264 evaluated_path = path.get_long_path_name(short_path)
272 evaluated_path = path.get_long_path_name(short_path)
265 assert evaluated_path.lower() == long_path.lower()
273 assert evaluated_path.lower() == long_path.lower()
266
274
267
275
268 @dec.skip_win32
276 @dec.skip_win32
269 def test_get_long_path_name():
277 def test_get_long_path_name():
270 p = path.get_long_path_name("/usr/local")
278 p = path.get_long_path_name("/usr/local")
271 assert p == "/usr/local"
279 assert p == "/usr/local"
272
280
273
281
274 class TestRaiseDeprecation(unittest.TestCase):
282 class TestRaiseDeprecation(unittest.TestCase):
275
283
276 @dec.skip_win32 # can't create not-user-writable dir on win
284 @dec.skip_win32 # can't create not-user-writable dir on win
277 @with_environment
285 @with_environment
278 def test_not_writable_ipdir(self):
286 def test_not_writable_ipdir(self):
279 tmpdir = tempfile.mkdtemp()
287 tmpdir = tempfile.mkdtemp()
280 os.name = "posix"
288 os.name = "posix"
281 env.pop('IPYTHON_DIR', None)
289 env.pop('IPYTHON_DIR', None)
282 env.pop('IPYTHONDIR', None)
290 env.pop('IPYTHONDIR', None)
283 env.pop('XDG_CONFIG_HOME', None)
291 env.pop('XDG_CONFIG_HOME', None)
284 env['HOME'] = tmpdir
292 env['HOME'] = tmpdir
285 ipdir = os.path.join(tmpdir, '.ipython')
293 ipdir = os.path.join(tmpdir, '.ipython')
286 os.mkdir(ipdir, 0o555)
294 os.mkdir(ipdir, 0o555)
287 try:
295 try:
288 open(os.path.join(ipdir, "_foo_"), 'w').close()
296 open(os.path.join(ipdir, "_foo_"), 'w').close()
289 except IOError:
297 except IOError:
290 pass
298 pass
291 else:
299 else:
292 # I can still write to an unwritable dir,
300 # I can still write to an unwritable dir,
293 # assume I'm root and skip the test
301 # assume I'm root and skip the test
294 raise SkipTest("I can't create directories that I can't write to")
302 pytest.skip("I can't create directories that I can't write to")
303
295 with self.assertWarnsRegex(UserWarning, 'is not a writable location'):
304 with self.assertWarnsRegex(UserWarning, 'is not a writable location'):
296 ipdir = paths.get_ipython_dir()
305 ipdir = paths.get_ipython_dir()
297 env.pop('IPYTHON_DIR', None)
306 env.pop('IPYTHON_DIR', None)
298
307
299 @with_environment
308 @with_environment
300 def test_get_py_filename():
309 def test_get_py_filename():
301 os.chdir(TMP_TEST_DIR)
310 os.chdir(TMP_TEST_DIR)
302 with make_tempfile("foo.py"):
311 with make_tempfile("foo.py"):
303 assert path.get_py_filename("foo.py") == "foo.py"
312 assert path.get_py_filename("foo.py") == "foo.py"
304 assert path.get_py_filename("foo") == "foo.py"
313 assert path.get_py_filename("foo") == "foo.py"
305 with make_tempfile("foo"):
314 with make_tempfile("foo"):
306 assert path.get_py_filename("foo") == "foo"
315 assert path.get_py_filename("foo") == "foo"
307 pytest.raises(IOError, path.get_py_filename, "foo.py")
316 pytest.raises(IOError, path.get_py_filename, "foo.py")
308 pytest.raises(IOError, path.get_py_filename, "foo")
317 pytest.raises(IOError, path.get_py_filename, "foo")
309 pytest.raises(IOError, path.get_py_filename, "foo.py")
318 pytest.raises(IOError, path.get_py_filename, "foo.py")
310 true_fn = "foo with spaces.py"
319 true_fn = "foo with spaces.py"
311 with make_tempfile(true_fn):
320 with make_tempfile(true_fn):
312 assert path.get_py_filename("foo with spaces") == true_fn
321 assert path.get_py_filename("foo with spaces") == true_fn
313 assert path.get_py_filename("foo with spaces.py") == true_fn
322 assert path.get_py_filename("foo with spaces.py") == true_fn
314 pytest.raises(IOError, path.get_py_filename, '"foo with spaces.py"')
323 pytest.raises(IOError, path.get_py_filename, '"foo with spaces.py"')
315 pytest.raises(IOError, path.get_py_filename, "'foo with spaces.py'")
324 pytest.raises(IOError, path.get_py_filename, "'foo with spaces.py'")
316
325
317 @onlyif_unicode_paths
326 @onlyif_unicode_paths
318 def test_unicode_in_filename():
327 def test_unicode_in_filename():
319 """When a file doesn't exist, the exception raised should be safe to call
328 """When a file doesn't exist, the exception raised should be safe to call
320 str() on - i.e. in Python 2 it must only have ASCII characters.
329 str() on - i.e. in Python 2 it must only have ASCII characters.
321
330
322 https://github.com/ipython/ipython/issues/875
331 https://github.com/ipython/ipython/issues/875
323 """
332 """
324 try:
333 try:
325 # these calls should not throw unicode encode exceptions
334 # these calls should not throw unicode encode exceptions
326 path.get_py_filename('fooéè.py')
335 path.get_py_filename('fooéè.py')
327 except IOError as ex:
336 except IOError as ex:
328 str(ex)
337 str(ex)
329
338
330
339
331 class TestShellGlob(unittest.TestCase):
340 class TestShellGlob(unittest.TestCase):
332
341
333 @classmethod
342 @classmethod
334 def setUpClass(cls):
343 def setUpClass(cls):
335 cls.filenames_start_with_a = ['a0', 'a1', 'a2']
344 cls.filenames_start_with_a = ['a0', 'a1', 'a2']
336 cls.filenames_end_with_b = ['0b', '1b', '2b']
345 cls.filenames_end_with_b = ['0b', '1b', '2b']
337 cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b
346 cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b
338 cls.tempdir = TemporaryDirectory()
347 cls.tempdir = TemporaryDirectory()
339 td = cls.tempdir.name
348 td = cls.tempdir.name
340
349
341 with cls.in_tempdir():
350 with cls.in_tempdir():
342 # Create empty files
351 # Create empty files
343 for fname in cls.filenames:
352 for fname in cls.filenames:
344 open(os.path.join(td, fname), 'w').close()
353 open(os.path.join(td, fname), 'w').close()
345
354
346 @classmethod
355 @classmethod
347 def tearDownClass(cls):
356 def tearDownClass(cls):
348 cls.tempdir.cleanup()
357 cls.tempdir.cleanup()
349
358
350 @classmethod
359 @classmethod
351 @contextmanager
360 @contextmanager
352 def in_tempdir(cls):
361 def in_tempdir(cls):
353 save = os.getcwd()
362 save = os.getcwd()
354 try:
363 try:
355 os.chdir(cls.tempdir.name)
364 os.chdir(cls.tempdir.name)
356 yield
365 yield
357 finally:
366 finally:
358 os.chdir(save)
367 os.chdir(save)
359
368
360 def check_match(self, patterns, matches):
369 def check_match(self, patterns, matches):
361 with self.in_tempdir():
370 with self.in_tempdir():
362 # glob returns unordered list. that's why sorted is required.
371 # glob returns unordered list. that's why sorted is required.
363 assert sorted(path.shellglob(patterns)) == sorted(matches)
372 assert sorted(path.shellglob(patterns)) == sorted(matches)
364
373
365 def common_cases(self):
374 def common_cases(self):
366 return [
375 return [
367 (['*'], self.filenames),
376 (['*'], self.filenames),
368 (['a*'], self.filenames_start_with_a),
377 (['a*'], self.filenames_start_with_a),
369 (['*c'], ['*c']),
378 (['*c'], ['*c']),
370 (['*', 'a*', '*b', '*c'], self.filenames
379 (['*', 'a*', '*b', '*c'], self.filenames
371 + self.filenames_start_with_a
380 + self.filenames_start_with_a
372 + self.filenames_end_with_b
381 + self.filenames_end_with_b
373 + ['*c']),
382 + ['*c']),
374 (['a[012]'], self.filenames_start_with_a),
383 (['a[012]'], self.filenames_start_with_a),
375 ]
384 ]
376
385
377 @skip_win32
386 @skip_win32
378 def test_match_posix(self):
387 def test_match_posix(self):
379 for (patterns, matches) in self.common_cases() + [
388 for (patterns, matches) in self.common_cases() + [
380 ([r'\*'], ['*']),
389 ([r'\*'], ['*']),
381 ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a),
390 ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a),
382 ([r'a\[012]'], ['a[012]']),
391 ([r'a\[012]'], ['a[012]']),
383 ]:
392 ]:
384 yield (self.check_match, patterns, matches)
393 yield (self.check_match, patterns, matches)
385
394
386 @skip_if_not_win32
395 @skip_if_not_win32
387 def test_match_windows(self):
396 def test_match_windows(self):
388 for (patterns, matches) in self.common_cases() + [
397 for (patterns, matches) in self.common_cases() + [
389 # In windows, backslash is interpreted as path
398 # In windows, backslash is interpreted as path
390 # separator. Therefore, you can't escape glob
399 # separator. Therefore, you can't escape glob
391 # using it.
400 # using it.
392 ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a),
401 ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a),
393 ([r'a\[012]'], [r'a\[012]']),
402 ([r'a\[012]'], [r'a\[012]']),
394 ]:
403 ]:
395 yield (self.check_match, patterns, matches)
404 yield (self.check_match, patterns, matches)
396
405
397
406
398 # TODO : pytest.mark.parametrise once nose is gone.
407 # TODO : pytest.mark.parametrise once nose is gone.
399 def test_unescape_glob():
408 def test_unescape_glob():
400 assert path.unescape_glob(r"\*\[\!\]\?") == "*[!]?"
409 assert path.unescape_glob(r"\*\[\!\]\?") == "*[!]?"
401 assert path.unescape_glob(r"\\*") == r"\*"
410 assert path.unescape_glob(r"\\*") == r"\*"
402 assert path.unescape_glob(r"\\\*") == r"\*"
411 assert path.unescape_glob(r"\\\*") == r"\*"
403 assert path.unescape_glob(r"\\a") == r"\a"
412 assert path.unescape_glob(r"\\a") == r"\a"
404 assert path.unescape_glob(r"\a") == r"\a"
413 assert path.unescape_glob(r"\a") == r"\a"
405
414
406
415
407 @onlyif_unicode_paths
416 @onlyif_unicode_paths
408 def test_ensure_dir_exists():
417 def test_ensure_dir_exists():
409 with TemporaryDirectory() as td:
418 with TemporaryDirectory() as td:
410 d = os.path.join(td, '∂ir')
419 d = os.path.join(td, '∂ir')
411 path.ensure_dir_exists(d) # create it
420 path.ensure_dir_exists(d) # create it
412 assert os.path.isdir(d)
421 assert os.path.isdir(d)
413 path.ensure_dir_exists(d) # no-op
422 path.ensure_dir_exists(d) # no-op
414 f = os.path.join(td, 'ƒile')
423 f = os.path.join(td, 'ƒile')
415 open(f, 'w').close() # touch
424 open(f, 'w').close() # touch
416 with pytest.raises(IOError):
425 with pytest.raises(IOError):
417 path.ensure_dir_exists(f)
426 path.ensure_dir_exists(f)
418
427
419 class TestLinkOrCopy(unittest.TestCase):
428 class TestLinkOrCopy(unittest.TestCase):
420 def setUp(self):
429 def setUp(self):
421 self.tempdir = TemporaryDirectory()
430 self.tempdir = TemporaryDirectory()
422 self.src = self.dst("src")
431 self.src = self.dst("src")
423 with open(self.src, "w") as f:
432 with open(self.src, "w") as f:
424 f.write("Hello, world!")
433 f.write("Hello, world!")
425
434
426 def tearDown(self):
435 def tearDown(self):
427 self.tempdir.cleanup()
436 self.tempdir.cleanup()
428
437
429 def dst(self, *args):
438 def dst(self, *args):
430 return os.path.join(self.tempdir.name, *args)
439 return os.path.join(self.tempdir.name, *args)
431
440
432 def assert_inode_not_equal(self, a, b):
441 def assert_inode_not_equal(self, a, b):
433 assert (
442 assert (
434 os.stat(a).st_ino != os.stat(b).st_ino
443 os.stat(a).st_ino != os.stat(b).st_ino
435 ), "%r and %r do reference the same indoes" % (a, b)
444 ), "%r and %r do reference the same indoes" % (a, b)
436
445
437 def assert_inode_equal(self, a, b):
446 def assert_inode_equal(self, a, b):
438 assert (
447 assert (
439 os.stat(a).st_ino == os.stat(b).st_ino
448 os.stat(a).st_ino == os.stat(b).st_ino
440 ), "%r and %r do not reference the same indoes" % (a, b)
449 ), "%r and %r do not reference the same indoes" % (a, b)
441
450
442 def assert_content_equal(self, a, b):
451 def assert_content_equal(self, a, b):
443 with open(a) as a_f:
452 with open(a) as a_f:
444 with open(b) as b_f:
453 with open(b) as b_f:
445 assert a_f.read() == b_f.read()
454 assert a_f.read() == b_f.read()
446
455
447 @skip_win32
456 @skip_win32
448 def test_link_successful(self):
457 def test_link_successful(self):
449 dst = self.dst("target")
458 dst = self.dst("target")
450 path.link_or_copy(self.src, dst)
459 path.link_or_copy(self.src, dst)
451 self.assert_inode_equal(self.src, dst)
460 self.assert_inode_equal(self.src, dst)
452
461
453 @skip_win32
462 @skip_win32
454 def test_link_into_dir(self):
463 def test_link_into_dir(self):
455 dst = self.dst("some_dir")
464 dst = self.dst("some_dir")
456 os.mkdir(dst)
465 os.mkdir(dst)
457 path.link_or_copy(self.src, dst)
466 path.link_or_copy(self.src, dst)
458 expected_dst = self.dst("some_dir", os.path.basename(self.src))
467 expected_dst = self.dst("some_dir", os.path.basename(self.src))
459 self.assert_inode_equal(self.src, expected_dst)
468 self.assert_inode_equal(self.src, expected_dst)
460
469
461 @skip_win32
470 @skip_win32
462 def test_target_exists(self):
471 def test_target_exists(self):
463 dst = self.dst("target")
472 dst = self.dst("target")
464 open(dst, "w").close()
473 open(dst, "w").close()
465 path.link_or_copy(self.src, dst)
474 path.link_or_copy(self.src, dst)
466 self.assert_inode_equal(self.src, dst)
475 self.assert_inode_equal(self.src, dst)
467
476
468 @skip_win32
477 @skip_win32
469 def test_no_link(self):
478 def test_no_link(self):
470 real_link = os.link
479 real_link = os.link
471 try:
480 try:
472 del os.link
481 del os.link
473 dst = self.dst("target")
482 dst = self.dst("target")
474 path.link_or_copy(self.src, dst)
483 path.link_or_copy(self.src, dst)
475 self.assert_content_equal(self.src, dst)
484 self.assert_content_equal(self.src, dst)
476 self.assert_inode_not_equal(self.src, dst)
485 self.assert_inode_not_equal(self.src, dst)
477 finally:
486 finally:
478 os.link = real_link
487 os.link = real_link
479
488
480 @skip_if_not_win32
489 @skip_if_not_win32
481 def test_windows(self):
490 def test_windows(self):
482 dst = self.dst("target")
491 dst = self.dst("target")
483 path.link_or_copy(self.src, dst)
492 path.link_or_copy(self.src, dst)
484 self.assert_content_equal(self.src, dst)
493 self.assert_content_equal(self.src, dst)
485
494
486 def test_link_twice(self):
495 def test_link_twice(self):
487 # Linking the same file twice shouldn't leave duplicates around.
496 # Linking the same file twice shouldn't leave duplicates around.
488 # See https://github.com/ipython/ipython/issues/6450
497 # See https://github.com/ipython/ipython/issues/6450
489 dst = self.dst('target')
498 dst = self.dst('target')
490 path.link_or_copy(self.src, dst)
499 path.link_or_copy(self.src, dst)
491 path.link_or_copy(self.src, dst)
500 path.link_or_copy(self.src, dst)
492 self.assert_inode_equal(self.src, dst)
501 self.assert_inode_equal(self.src, dst)
493 assert sorted(os.listdir(self.tempdir.name)) == ["src", "target"]
502 assert sorted(os.listdir(self.tempdir.name)) == ["src", "target"]
@@ -1,39 +1,35 b''
1 build: false
1 build: false
2 matrix:
2 matrix:
3 fast_finish: true # immediately finish build once one of the jobs fails.
3 fast_finish: true # immediately finish build once one of the jobs fails.
4
4
5 environment:
5 environment:
6 global:
6 global:
7 COLUMNS: 120 # Appveyor web viwer window width is 130 chars
7 COLUMNS: 120 # Appveyor web viwer window width is 130 chars
8
8
9 matrix:
9 matrix:
10
10
11 - PYTHON: "C:\\Python37-x64"
11 - PYTHON: "C:\\Python37-x64"
12 PYTHON_VERSION: "3.7.x"
12 PYTHON_VERSION: "3.7.x"
13 PYTHON_ARCH: "64"
13 PYTHON_ARCH: "64"
14
14
15 - PYTHON: "C:\\Python38"
15 - PYTHON: "C:\\Python38"
16 PYTHON_VERSION: "3.8.x"
16 PYTHON_VERSION: "3.8.x"
17 PYTHON_ARCH: "32"
17 PYTHON_ARCH: "32"
18
18
19 - PYTHON: "C:\\Python38-x64"
19 - PYTHON: "C:\\Python38-x64"
20 PYTHON_VERSION: "3.8.x"
20 PYTHON_VERSION: "3.8.x"
21 PYTHON_ARCH: "64"
21 PYTHON_ARCH: "64"
22
22
23 init:
23 init:
24 - "ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH%"
24 - "ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH%"
25
25
26 install:
26 install:
27 - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
27 - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
28 - python -m pip install --upgrade setuptools pip
28 - python -m pip install --upgrade setuptools pip
29 - pip install nose coverage pytest pytest-cov pytest-trio matplotlib pandas
29 - pip install pytest pytest-cov pytest-trio matplotlib pandas
30 - pip install -e .[test]
30 - pip install -e .[test]
31 - mkdir results
32 - cd results
33 test_script:
31 test_script:
34 - iptest --coverage xml
35 - cd ..
36 - pytest --color=yes -ra --cov --cov-report=xml
32 - pytest --color=yes -ra --cov --cov-report=xml
37 on_finish:
33 on_finish:
38 - curl -Os https://uploader.codecov.io/latest/windows/codecov.exe
34 - curl -Os https://uploader.codecov.io/latest/windows/codecov.exe
39 - codecov -e PYTHON_VERSION,PYTHON_ARCH
35 - codecov -e PYTHON_VERSION,PYTHON_ARCH
@@ -1,15 +1,14 b''
1 name: ipython_docs
1 name: ipython_docs
2 dependencies:
2 dependencies:
3 - python=3.8
3 - python=3.8
4 - setuptools>=18.5
4 - setuptools>=18.5
5 - sphinx>=1.8
5 - sphinx>=1.8
6 - sphinx_rtd_theme
6 - sphinx_rtd_theme
7 - numpy
7 - numpy
8 - nose
9 - testpath
8 - testpath
10 - matplotlib
9 - matplotlib
11 - pip:
10 - pip:
12 - docrepr
11 - docrepr
13 - prompt_toolkit
12 - prompt_toolkit
14 - ipykernel
13 - ipykernel
15 - stack_data
14 - stack_data
@@ -1,144 +1,143 b''
1 .. _install:
1 .. _install:
2
2
3 Installing IPython
3 Installing IPython
4 ==================
4 ==================
5
5
6
6
7 IPython 6 requires Python ≥ 3.3. IPython 5.x can be installed on Python 2.
7 IPython 6 requires Python ≥ 3.3. IPython 5.x can be installed on Python 2.
8
8
9
9
10 Quick Install
10 Quick Install
11 -------------
11 -------------
12
12
13 With ``pip`` already installed :
13 With ``pip`` already installed :
14
14
15 .. code-block:: bash
15 .. code-block:: bash
16
16
17 $ pip install ipython
17 $ pip install ipython
18
18
19 This installs IPython as well as its dependencies.
19 This installs IPython as well as its dependencies.
20
20
21 If you want to use IPython with notebooks or the Qt console, you should also
21 If you want to use IPython with notebooks or the Qt console, you should also
22 install Jupyter ``pip install jupyter``.
22 install Jupyter ``pip install jupyter``.
23
23
24
24
25
25
26 Overview
26 Overview
27 --------
27 --------
28
28
29 This document describes in detail the steps required to install IPython. For a
29 This document describes in detail the steps required to install IPython. For a
30 few quick ways to get started with package managers or full Python
30 few quick ways to get started with package managers or full Python
31 distributions, see `the install page <https://ipython.org/install.html>`_ of the
31 distributions, see `the install page <https://ipython.org/install.html>`_ of the
32 IPython website.
32 IPython website.
33
33
34 Please let us know if you have problems installing IPython or any of its
34 Please let us know if you have problems installing IPython or any of its
35 dependencies.
35 dependencies.
36
36
37 IPython and most dependencies should be installed via :command:`pip`.
37 IPython and most dependencies should be installed via :command:`pip`.
38 In many scenarios, this is the simplest method of installing Python packages.
38 In many scenarios, this is the simplest method of installing Python packages.
39 More information about :mod:`pip` can be found on
39 More information about :mod:`pip` can be found on
40 `its PyPI page <https://pip.pypa.io>`__.
40 `its PyPI page <https://pip.pypa.io>`__.
41
41
42
42
43 More general information about installing Python packages can be found in
43 More general information about installing Python packages can be found in
44 `Python's documentation <http://docs.python.org>`_.
44 `Python's documentation <http://docs.python.org>`_.
45
45
46 .. _dependencies:
46 .. _dependencies:
47
47
48 Dependencies
48 Dependencies
49 ~~~~~~~~~~~~
49 ~~~~~~~~~~~~
50
50
51 IPython relies on a number of other Python packages. Installing using a package
51 IPython relies on a number of other Python packages. Installing using a package
52 manager like pip or conda will ensure the necessary packages are installed.
52 manager like pip or conda will ensure the necessary packages are installed.
53 Manual installation without dependencies is possible, but not recommended.
53 Manual installation without dependencies is possible, but not recommended.
54 The dependencies can be viewed with package manager commands,
54 The dependencies can be viewed with package manager commands,
55 such as :command:`pip show ipython` or :command:`conda info ipython`.
55 such as :command:`pip show ipython` or :command:`conda info ipython`.
56
56
57
57
58 Installing IPython itself
58 Installing IPython itself
59 ~~~~~~~~~~~~~~~~~~~~~~~~~
59 ~~~~~~~~~~~~~~~~~~~~~~~~~
60
60
61 IPython requires several dependencies to work correctly, it is not recommended
61 IPython requires several dependencies to work correctly, it is not recommended
62 to install IPython and all its dependencies manually as this can be quite long
62 to install IPython and all its dependencies manually as this can be quite long
63 and troublesome. You should use the python package manager ``pip``.
63 and troublesome. You should use the python package manager ``pip``.
64
64
65 Installation using pip
65 Installation using pip
66 ~~~~~~~~~~~~~~~~~~~~~~
66 ~~~~~~~~~~~~~~~~~~~~~~
67
67
68 Make sure you have the latest version of :mod:`pip` (the Python package
68 Make sure you have the latest version of :mod:`pip` (the Python package
69 manager) installed. If you do not, head to `Pip documentation
69 manager) installed. If you do not, head to `Pip documentation
70 <https://pip.pypa.io/en/stable/installing/>`_ and install :mod:`pip` first.
70 <https://pip.pypa.io/en/stable/installing/>`_ and install :mod:`pip` first.
71
71
72 The quickest way to get up and running with IPython is to install it with pip:
72 The quickest way to get up and running with IPython is to install it with pip:
73
73
74 .. code-block:: bash
74 .. code-block:: bash
75
75
76 $ pip install ipython
76 $ pip install ipython
77
77
78 That's it.
78 That's it.
79
79
80
80
81 Installation from source
81 Installation from source
82 ~~~~~~~~~~~~~~~~~~~~~~~~
82 ~~~~~~~~~~~~~~~~~~~~~~~~
83
83
84 To install IPython from source,
84 To install IPython from source,
85 grab the latest stable tarball of IPython `from PyPI
85 grab the latest stable tarball of IPython `from PyPI
86 <https://pypi.python.org/pypi/ipython>`__. Then do the following:
86 <https://pypi.python.org/pypi/ipython>`__. Then do the following:
87
87
88 .. code-block:: bash
88 .. code-block:: bash
89
89
90 tar -xzf ipython-5.1.0.tar.gz
90 tar -xzf ipython-5.1.0.tar.gz
91 cd ipython-5.1.0
91 cd ipython-5.1.0
92 # The [test] extra ensures test dependencies are installed too:
92 # The [test] extra ensures test dependencies are installed too:
93 pip install '.[test]'
93 pip install '.[test]'
94
94
95 Do not invoke ``setup.py`` directly as this can have undesirable consequences
95 Do not invoke ``setup.py`` directly as this can have undesirable consequences
96 for further upgrades. We do not recommend using ``easy_install`` either.
96 for further upgrades. We do not recommend using ``easy_install`` either.
97
97
98 If you are installing to a location (like ``/usr/local``) that requires higher
98 If you are installing to a location (like ``/usr/local``) that requires higher
99 permissions, you may need to run the last command with :command:`sudo`. You can
99 permissions, you may need to run the last command with :command:`sudo`. You can
100 also install in user specific location by using the ``--user`` flag in
100 also install in user specific location by using the ``--user`` flag in
101 conjunction with pip.
101 conjunction with pip.
102
102
103 To run IPython's test suite, use the :command:`iptest` command from outside of
103 To run IPython's test suite, use the :command:`pytest` command:
104 the IPython source tree:
105
104
106 .. code-block:: bash
105 .. code-block:: bash
107
106
108 $ iptest
107 $ pytest
109
108
110 .. _devinstall:
109 .. _devinstall:
111
110
112 Installing the development version
111 Installing the development version
113 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
112 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
114
113
115 It is also possible to install the development version of IPython from our
114 It is also possible to install the development version of IPython from our
116 `Git <http://git-scm.com/>`_ source code repository. To do this you will
115 `Git <http://git-scm.com/>`_ source code repository. To do this you will
117 need to have Git installed on your system.
116 need to have Git installed on your system.
118
117
119
118
120 Then do:
119 Then do:
121
120
122 .. code-block:: bash
121 .. code-block:: bash
123
122
124 $ git clone https://github.com/ipython/ipython.git
123 $ git clone https://github.com/ipython/ipython.git
125 $ cd ipython
124 $ cd ipython
126 $ pip install -e '.[test]'
125 $ pip install -e '.[test]'
127
126
128 The :command:`pip install -e .` command allows users and developers to follow
127 The :command:`pip install -e .` command allows users and developers to follow
129 the development branch as it changes by creating links in the right places and
128 the development branch as it changes by creating links in the right places and
130 installing the command line scripts to the appropriate locations.
129 installing the command line scripts to the appropriate locations.
131
130
132 Then, if you want to update your IPython at any time, do:
131 Then, if you want to update your IPython at any time, do:
133
132
134 .. code-block:: bash
133 .. code-block:: bash
135
134
136 $ git pull
135 $ git pull
137
136
138 If the dependencies or entrypoints have changed, you may have to run
137 If the dependencies or entrypoints have changed, you may have to run
139
138
140 .. code-block:: bash
139 .. code-block:: bash
141
140
142 $ pip install -e .
141 $ pip install -e .
143
142
144 again, but this is infrequent.
143 again, but this is infrequent.
@@ -1,273 +1,272 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
2 # -*- coding: utf-8 -*-
3 """Setup script for IPython.
3 """Setup script for IPython.
4
4
5 Under Posix environments it works like a typical setup.py script.
5 Under Posix environments it works like a typical setup.py script.
6 Under Windows, the command sdist is not supported, since IPython
6 Under Windows, the command sdist is not supported, since IPython
7 requires utilities which are not available under Windows."""
7 requires utilities which are not available under Windows."""
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (c) 2008-2011, IPython Development Team.
10 # Copyright (c) 2008-2011, IPython Development Team.
11 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
11 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
12 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
12 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
13 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
13 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
14 #
14 #
15 # Distributed under the terms of the Modified BSD License.
15 # Distributed under the terms of the Modified BSD License.
16 #
16 #
17 # The full license is in the file COPYING.rst, distributed with this software.
17 # The full license is in the file COPYING.rst, distributed with this software.
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 import os
20 import os
21 import sys
21 import sys
22 from pathlib import Path
22 from pathlib import Path
23
23
24 # **Python version check**
24 # **Python version check**
25 #
25 #
26 # This check is also made in IPython/__init__, don't forget to update both when
26 # This check is also made in IPython/__init__, don't forget to update both when
27 # changing Python version requirements.
27 # changing Python version requirements.
28 if sys.version_info < (3, 7):
28 if sys.version_info < (3, 7):
29 pip_message = 'This may be due to an out of date pip. Make sure you have pip >= 9.0.1.'
29 pip_message = 'This may be due to an out of date pip. Make sure you have pip >= 9.0.1.'
30 try:
30 try:
31 import pip
31 import pip
32 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
32 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
33 if pip_version < (9, 0, 1) :
33 if pip_version < (9, 0, 1) :
34 pip_message = 'Your pip version is out of date, please install pip >= 9.0.1. '\
34 pip_message = 'Your pip version is out of date, please install pip >= 9.0.1. '\
35 'pip {} detected.'.format(pip.__version__)
35 'pip {} detected.'.format(pip.__version__)
36 else:
36 else:
37 # pip is new enough - it must be something else
37 # pip is new enough - it must be something else
38 pip_message = ''
38 pip_message = ''
39 except Exception:
39 except Exception:
40 pass
40 pass
41
41
42
42
43 error = """
43 error = """
44 IPython 7.17+ supports Python 3.7 and above, following NEP 29.
44 IPython 7.17+ supports Python 3.7 and above, following NEP 29.
45 When using Python 2.7, please install IPython 5.x LTS Long Term Support version.
45 When using Python 2.7, please install IPython 5.x LTS Long Term Support version.
46 Python 3.3 and 3.4 were supported up to IPython 6.x.
46 Python 3.3 and 3.4 were supported up to IPython 6.x.
47 Python 3.5 was supported with IPython 7.0 to 7.9.
47 Python 3.5 was supported with IPython 7.0 to 7.9.
48 Python 3.6 was supported with IPython up to 7.16.
48 Python 3.6 was supported with IPython up to 7.16.
49
49
50 See IPython `README.rst` file for more information:
50 See IPython `README.rst` file for more information:
51
51
52 https://github.com/ipython/ipython/blob/master/README.rst
52 https://github.com/ipython/ipython/blob/master/README.rst
53
53
54 Python {py} detected.
54 Python {py} detected.
55 {pip}
55 {pip}
56 """.format(py=sys.version_info, pip=pip_message )
56 """.format(py=sys.version_info, pip=pip_message )
57
57
58 print(error, file=sys.stderr)
58 print(error, file=sys.stderr)
59 sys.exit(1)
59 sys.exit(1)
60
60
61 # At least we're on the python version we need, move on.
61 # At least we're on the python version we need, move on.
62
62
63 # BEFORE importing distutils, remove MANIFEST. distutils doesn't properly
63 # BEFORE importing distutils, remove MANIFEST. distutils doesn't properly
64 # update it when the contents of directories change.
64 # update it when the contents of directories change.
65 if Path("MANIFEST").exists():
65 if Path("MANIFEST").exists():
66 Path("MANIFEST").unlink()
66 Path("MANIFEST").unlink()
67
67
68 from distutils.core import setup
68 from distutils.core import setup
69
69
70 # Our own imports
70 # Our own imports
71 from setupbase import target_update
71 from setupbase import target_update
72
72
73 from setupbase import (
73 from setupbase import (
74 setup_args,
74 setup_args,
75 find_packages,
75 find_packages,
76 find_package_data,
76 find_package_data,
77 check_package_data_first,
77 check_package_data_first,
78 find_entry_points,
78 find_entry_points,
79 build_scripts_entrypt,
79 build_scripts_entrypt,
80 find_data_files,
80 find_data_files,
81 git_prebuild,
81 git_prebuild,
82 install_symlinked,
82 install_symlinked,
83 install_lib_symlink,
83 install_lib_symlink,
84 install_scripts_for_symlink,
84 install_scripts_for_symlink,
85 unsymlink,
85 unsymlink,
86 )
86 )
87
87
88 #-------------------------------------------------------------------------------
88 #-------------------------------------------------------------------------------
89 # Handle OS specific things
89 # Handle OS specific things
90 #-------------------------------------------------------------------------------
90 #-------------------------------------------------------------------------------
91
91
92 if os.name in ('nt','dos'):
92 if os.name in ('nt','dos'):
93 os_name = 'windows'
93 os_name = 'windows'
94 else:
94 else:
95 os_name = os.name
95 os_name = os.name
96
96
97 # Under Windows, 'sdist' has not been supported. Now that the docs build with
97 # Under Windows, 'sdist' has not been supported. Now that the docs build with
98 # Sphinx it might work, but let's not turn it on until someone confirms that it
98 # Sphinx it might work, but let's not turn it on until someone confirms that it
99 # actually works.
99 # actually works.
100 if os_name == 'windows' and 'sdist' in sys.argv:
100 if os_name == 'windows' and 'sdist' in sys.argv:
101 print('The sdist command is not available under Windows. Exiting.')
101 print('The sdist command is not available under Windows. Exiting.')
102 sys.exit(1)
102 sys.exit(1)
103
103
104
104
105 #-------------------------------------------------------------------------------
105 #-------------------------------------------------------------------------------
106 # Things related to the IPython documentation
106 # Things related to the IPython documentation
107 #-------------------------------------------------------------------------------
107 #-------------------------------------------------------------------------------
108
108
109 # update the manuals when building a source dist
109 # update the manuals when building a source dist
110 if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'):
110 if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'):
111
111
112 # List of things to be updated. Each entry is a triplet of args for
112 # List of things to be updated. Each entry is a triplet of args for
113 # target_update()
113 # target_update()
114 to_update = [
114 to_update = [
115 ('docs/man/ipython.1.gz',
115 ('docs/man/ipython.1.gz',
116 ['docs/man/ipython.1'],
116 ['docs/man/ipython.1'],
117 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz'),
117 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz'),
118 ]
118 ]
119
119
120
120
121 [ target_update(*t) for t in to_update ]
121 [ target_update(*t) for t in to_update ]
122
122
123 #---------------------------------------------------------------------------
123 #---------------------------------------------------------------------------
124 # Find all the packages, package data, and data_files
124 # Find all the packages, package data, and data_files
125 #---------------------------------------------------------------------------
125 #---------------------------------------------------------------------------
126
126
127 packages = find_packages()
127 packages = find_packages()
128 package_data = find_package_data()
128 package_data = find_package_data()
129
129
130 data_files = find_data_files()
130 data_files = find_data_files()
131
131
132 setup_args['packages'] = packages
132 setup_args['packages'] = packages
133 setup_args['package_data'] = package_data
133 setup_args['package_data'] = package_data
134 setup_args['data_files'] = data_files
134 setup_args['data_files'] = data_files
135
135
136 #---------------------------------------------------------------------------
136 #---------------------------------------------------------------------------
137 # custom distutils commands
137 # custom distutils commands
138 #---------------------------------------------------------------------------
138 #---------------------------------------------------------------------------
139 # imports here, so they are after setuptools import if there was one
139 # imports here, so they are after setuptools import if there was one
140 from distutils.command.sdist import sdist
140 from distutils.command.sdist import sdist
141
141
142 setup_args['cmdclass'] = {
142 setup_args['cmdclass'] = {
143 'build_py': \
143 'build_py': \
144 check_package_data_first(git_prebuild('IPython')),
144 check_package_data_first(git_prebuild('IPython')),
145 'sdist' : git_prebuild('IPython', sdist),
145 'sdist' : git_prebuild('IPython', sdist),
146 'symlink': install_symlinked,
146 'symlink': install_symlinked,
147 'install_lib_symlink': install_lib_symlink,
147 'install_lib_symlink': install_lib_symlink,
148 'install_scripts_sym': install_scripts_for_symlink,
148 'install_scripts_sym': install_scripts_for_symlink,
149 'unsymlink': unsymlink,
149 'unsymlink': unsymlink,
150 }
150 }
151
151
152
152
153 #---------------------------------------------------------------------------
153 #---------------------------------------------------------------------------
154 # Handle scripts, dependencies, and setuptools specific things
154 # Handle scripts, dependencies, and setuptools specific things
155 #---------------------------------------------------------------------------
155 #---------------------------------------------------------------------------
156
156
157 # For some commands, use setuptools. Note that we do NOT list install here!
157 # For some commands, use setuptools. Note that we do NOT list install here!
158 # If you want a setuptools-enhanced install, just run 'setupegg.py install'
158 # If you want a setuptools-enhanced install, just run 'setupegg.py install'
159 needs_setuptools = {'develop', 'release', 'bdist_egg', 'bdist_rpm',
159 needs_setuptools = {'develop', 'release', 'bdist_egg', 'bdist_rpm',
160 'bdist', 'bdist_dumb', 'bdist_wininst', 'bdist_wheel',
160 'bdist', 'bdist_dumb', 'bdist_wininst', 'bdist_wheel',
161 'egg_info', 'easy_install', 'upload', 'install_egg_info',
161 'egg_info', 'easy_install', 'upload', 'install_egg_info',
162 }
162 }
163
163
164 if len(needs_setuptools.intersection(sys.argv)) > 0:
164 if len(needs_setuptools.intersection(sys.argv)) > 0:
165 import setuptools
165 import setuptools
166
166
167 # This dict is used for passing extra arguments that are setuptools
167 # This dict is used for passing extra arguments that are setuptools
168 # specific to setup
168 # specific to setup
169 setuptools_extra_args = {}
169 setuptools_extra_args = {}
170
170
171 # setuptools requirements
171 # setuptools requirements
172
172
173 extras_require = dict(
173 extras_require = dict(
174 parallel=["ipyparallel"],
174 parallel=["ipyparallel"],
175 qtconsole=["qtconsole"],
175 qtconsole=["qtconsole"],
176 doc=["Sphinx>=1.3"],
176 doc=["Sphinx>=1.3"],
177 test=[
177 test=[
178 "nose>=0.10.1",
179 "pytest",
178 "pytest",
180 "requests",
179 "requests",
181 "testpath",
180 "testpath",
182 "pygments",
181 "pygments",
183 "nbformat",
182 "nbformat",
184 "ipykernel",
183 "ipykernel",
185 "numpy>=1.17",
184 "numpy>=1.17",
186 ],
185 ],
187 terminal=[],
186 terminal=[],
188 kernel=["ipykernel"],
187 kernel=["ipykernel"],
189 nbformat=["nbformat"],
188 nbformat=["nbformat"],
190 notebook=["notebook", "ipywidgets"],
189 notebook=["notebook", "ipywidgets"],
191 nbconvert=["nbconvert"],
190 nbconvert=["nbconvert"],
192 )
191 )
193
192
194 install_requires = [
193 install_requires = [
195 "setuptools>=18.5",
194 "setuptools>=18.5",
196 "jedi>=0.16",
195 "jedi>=0.16",
197 "decorator",
196 "decorator",
198 "pickleshare",
197 "pickleshare",
199 "traitlets>=4.2",
198 "traitlets>=4.2",
200 "prompt_toolkit>=2.0.0,<3.1.0,!=3.0.0,!=3.0.1",
199 "prompt_toolkit>=2.0.0,<3.1.0,!=3.0.0,!=3.0.1",
201 "pygments",
200 "pygments",
202 "backcall",
201 "backcall",
203 "stack_data",
202 "stack_data",
204 "matplotlib-inline",
203 "matplotlib-inline",
205 ]
204 ]
206
205
207 # Platform-specific dependencies:
206 # Platform-specific dependencies:
208 # This is the correct way to specify these,
207 # This is the correct way to specify these,
209 # but requires pip >= 6. pip < 6 ignores these.
208 # but requires pip >= 6. pip < 6 ignores these.
210
209
211 extras_require.update(
210 extras_require.update(
212 {
211 {
213 ':sys_platform != "win32"': ["pexpect>4.3"],
212 ':sys_platform != "win32"': ["pexpect>4.3"],
214 ':sys_platform == "darwin"': ["appnope"],
213 ':sys_platform == "darwin"': ["appnope"],
215 ':sys_platform == "win32"': ["colorama"],
214 ':sys_platform == "win32"': ["colorama"],
216 }
215 }
217 )
216 )
218 # FIXME: re-specify above platform dependencies for pip < 6
217 # FIXME: re-specify above platform dependencies for pip < 6
219 # These would result in non-portable bdists.
218 # These would result in non-portable bdists.
220 if not any(arg.startswith('bdist') for arg in sys.argv):
219 if not any(arg.startswith('bdist') for arg in sys.argv):
221 if sys.platform == 'darwin':
220 if sys.platform == 'darwin':
222 install_requires.extend(['appnope'])
221 install_requires.extend(['appnope'])
223
222
224 if not sys.platform.startswith("win"):
223 if not sys.platform.startswith("win"):
225 install_requires.append("pexpect>4.3")
224 install_requires.append("pexpect>4.3")
226
225
227 # workaround pypa/setuptools#147, where setuptools misspells
226 # workaround pypa/setuptools#147, where setuptools misspells
228 # platform_python_implementation as python_implementation
227 # platform_python_implementation as python_implementation
229 if 'setuptools' in sys.modules:
228 if 'setuptools' in sys.modules:
230 for key in list(extras_require):
229 for key in list(extras_require):
231 if 'platform_python_implementation' in key:
230 if 'platform_python_implementation' in key:
232 new_key = key.replace('platform_python_implementation', 'python_implementation')
231 new_key = key.replace('platform_python_implementation', 'python_implementation')
233 extras_require[new_key] = extras_require.pop(key)
232 extras_require[new_key] = extras_require.pop(key)
234
233
235 everything = set()
234 everything = set()
236 for key, deps in extras_require.items():
235 for key, deps in extras_require.items():
237 if ':' not in key:
236 if ':' not in key:
238 everything.update(deps)
237 everything.update(deps)
239 extras_require['all'] = list(sorted(everything))
238 extras_require['all'] = list(sorted(everything))
240
239
241 if 'setuptools' in sys.modules:
240 if 'setuptools' in sys.modules:
242 setuptools_extra_args['python_requires'] = '>=3.7'
241 setuptools_extra_args['python_requires'] = '>=3.7'
243 setuptools_extra_args['zip_safe'] = False
242 setuptools_extra_args['zip_safe'] = False
244 setuptools_extra_args['entry_points'] = {
243 setuptools_extra_args['entry_points'] = {
245 'console_scripts': find_entry_points(),
244 'console_scripts': find_entry_points(),
246 'pygments.lexers': [
245 'pygments.lexers': [
247 'ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer',
246 'ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer',
248 'ipython = IPython.lib.lexers:IPythonLexer',
247 'ipython = IPython.lib.lexers:IPythonLexer',
249 'ipython3 = IPython.lib.lexers:IPython3Lexer',
248 'ipython3 = IPython.lib.lexers:IPython3Lexer',
250 ],
249 ],
251 }
250 }
252 setup_args['extras_require'] = extras_require
251 setup_args['extras_require'] = extras_require
253 setup_args['install_requires'] = install_requires
252 setup_args['install_requires'] = install_requires
254
253
255 else:
254 else:
256 # scripts has to be a non-empty list, or install_scripts isn't called
255 # scripts has to be a non-empty list, or install_scripts isn't called
257 setup_args['scripts'] = [e.split('=')[0].strip() for e in find_entry_points()]
256 setup_args['scripts'] = [e.split('=')[0].strip() for e in find_entry_points()]
258
257
259 setup_args['cmdclass']['build_scripts'] = build_scripts_entrypt
258 setup_args['cmdclass']['build_scripts'] = build_scripts_entrypt
260
259
261 #---------------------------------------------------------------------------
260 #---------------------------------------------------------------------------
262 # Do the actual setup now
261 # Do the actual setup now
263 #---------------------------------------------------------------------------
262 #---------------------------------------------------------------------------
264
263
265 setup_args.update(setuptools_extra_args)
264 setup_args.update(setuptools_extra_args)
266
265
267
266
268
267
269 def main():
268 def main():
270 setup(**setup_args)
269 setup(**setup_args)
271
270
272 if __name__ == '__main__':
271 if __name__ == '__main__':
273 main()
272 main()
@@ -1,408 +1,407 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 This module defines the things that are used in setup.py for building IPython
3 This module defines the things that are used in setup.py for building IPython
4
4
5 This includes:
5 This includes:
6
6
7 * The basic arguments to setup
7 * The basic arguments to setup
8 * Functions for finding things like packages, package data, etc.
8 * Functions for finding things like packages, package data, etc.
9 * A function for checking dependencies.
9 * A function for checking dependencies.
10 """
10 """
11
11
12 # Copyright (c) IPython Development Team.
12 # Copyright (c) IPython Development Team.
13 # Distributed under the terms of the Modified BSD License.
13 # Distributed under the terms of the Modified BSD License.
14
14
15 import os
15 import os
16 import re
16 import re
17 import sys
17 import sys
18 from glob import glob
18 from glob import glob
19 from logging import log
19 from logging import log
20
20
21 from setuptools import Command
21 from setuptools import Command
22 from setuptools.command.build_py import build_py
22 from setuptools.command.build_py import build_py
23
23
24 # TODO: Replacement for this?
24 # TODO: Replacement for this?
25 from distutils.command.build_scripts import build_scripts
25 from distutils.command.build_scripts import build_scripts
26 from setuptools.command.install import install
26 from setuptools.command.install import install
27 from setuptools.command.install_scripts import install_scripts
27 from setuptools.command.install_scripts import install_scripts
28
28
29 from setupext import install_data_ext
29 from setupext import install_data_ext
30
30
31 #-------------------------------------------------------------------------------
31 #-------------------------------------------------------------------------------
32 # Useful globals and utility functions
32 # Useful globals and utility functions
33 #-------------------------------------------------------------------------------
33 #-------------------------------------------------------------------------------
34
34
35 # A few handy globals
35 # A few handy globals
36 isfile = os.path.isfile
36 isfile = os.path.isfile
37 pjoin = os.path.join
37 pjoin = os.path.join
38 repo_root = os.path.dirname(os.path.abspath(__file__))
38 repo_root = os.path.dirname(os.path.abspath(__file__))
39
39
40 def execfile(fname, globs, locs=None):
40 def execfile(fname, globs, locs=None):
41 locs = locs or globs
41 locs = locs or globs
42 with open(fname) as f:
42 with open(fname) as f:
43 exec(compile(f.read(), fname, "exec"), globs, locs)
43 exec(compile(f.read(), fname, "exec"), globs, locs)
44
44
45 # A little utility we'll need below, since glob() does NOT allow you to do
45 # A little utility we'll need below, since glob() does NOT allow you to do
46 # exclusion on multiple endings!
46 # exclusion on multiple endings!
47 def file_doesnt_endwith(test,endings):
47 def file_doesnt_endwith(test,endings):
48 """Return true if test is a file and its name does NOT end with any
48 """Return true if test is a file and its name does NOT end with any
49 of the strings listed in endings."""
49 of the strings listed in endings."""
50 if not isfile(test):
50 if not isfile(test):
51 return False
51 return False
52 for e in endings:
52 for e in endings:
53 if test.endswith(e):
53 if test.endswith(e):
54 return False
54 return False
55 return True
55 return True
56
56
57 #---------------------------------------------------------------------------
57 #---------------------------------------------------------------------------
58 # Basic project information
58 # Basic project information
59 #---------------------------------------------------------------------------
59 #---------------------------------------------------------------------------
60
60
61 # release.py contains version, authors, license, url, keywords, etc.
61 # release.py contains version, authors, license, url, keywords, etc.
62 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
62 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
63
63
64 # Create a dict with the basic information
64 # Create a dict with the basic information
65 # This dict is eventually passed to setup after additional keys are added.
65 # This dict is eventually passed to setup after additional keys are added.
66 setup_args = dict(
66 setup_args = dict(
67 name = name,
67 name = name,
68 version = version,
68 version = version,
69 description = description,
69 description = description,
70 long_description = long_description,
70 long_description = long_description,
71 author = author,
71 author = author,
72 author_email = author_email,
72 author_email = author_email,
73 url = url,
73 url = url,
74 license = license,
74 license = license,
75 platforms = platforms,
75 platforms = platforms,
76 keywords = keywords,
76 keywords = keywords,
77 classifiers = classifiers,
77 classifiers = classifiers,
78 cmdclass = {'install_data': install_data_ext},
78 cmdclass = {'install_data': install_data_ext},
79 project_urls={
79 project_urls={
80 'Documentation': 'https://ipython.readthedocs.io/',
80 'Documentation': 'https://ipython.readthedocs.io/',
81 'Funding' : 'https://numfocus.org/',
81 'Funding' : 'https://numfocus.org/',
82 'Source' : 'https://github.com/ipython/ipython',
82 'Source' : 'https://github.com/ipython/ipython',
83 'Tracker' : 'https://github.com/ipython/ipython/issues',
83 'Tracker' : 'https://github.com/ipython/ipython/issues',
84 }
84 }
85 )
85 )
86
86
87
87
88 #---------------------------------------------------------------------------
88 #---------------------------------------------------------------------------
89 # Find packages
89 # Find packages
90 #---------------------------------------------------------------------------
90 #---------------------------------------------------------------------------
91
91
92 def find_packages():
92 def find_packages():
93 """
93 """
94 Find all of IPython's packages.
94 Find all of IPython's packages.
95 """
95 """
96 excludes = ['deathrow', 'quarantine']
96 excludes = ['deathrow', 'quarantine']
97 packages = []
97 packages = []
98 for directory, subdirs, files in os.walk("IPython"):
98 for directory, subdirs, files in os.walk("IPython"):
99 package = directory.replace(os.path.sep, ".")
99 package = directory.replace(os.path.sep, ".")
100 if any(package.startswith("IPython." + exc) for exc in excludes):
100 if any(package.startswith("IPython." + exc) for exc in excludes):
101 # package is to be excluded (e.g. deathrow)
101 # package is to be excluded (e.g. deathrow)
102 continue
102 continue
103 if '__init__.py' not in files:
103 if '__init__.py' not in files:
104 # not a package
104 # not a package
105 continue
105 continue
106 packages.append(package)
106 packages.append(package)
107 return packages
107 return packages
108
108
109 #---------------------------------------------------------------------------
109 #---------------------------------------------------------------------------
110 # Find package data
110 # Find package data
111 #---------------------------------------------------------------------------
111 #---------------------------------------------------------------------------
112
112
113 def find_package_data():
113 def find_package_data():
114 """
114 """
115 Find IPython's package_data.
115 Find IPython's package_data.
116 """
116 """
117 # This is not enough for these things to appear in an sdist.
117 # This is not enough for these things to appear in an sdist.
118 # We need to muck with the MANIFEST to get this to work
118 # We need to muck with the MANIFEST to get this to work
119
119
120 package_data = {
120 package_data = {
121 'IPython.core' : ['profile/README*'],
121 'IPython.core' : ['profile/README*'],
122 'IPython.core.tests' : ['*.png', '*.jpg', 'daft_extension/*.py'],
122 'IPython.core.tests' : ['*.png', '*.jpg', 'daft_extension/*.py'],
123 'IPython.lib.tests' : ['*.wav'],
123 'IPython.lib.tests' : ['*.wav'],
124 'IPython.testing.plugin' : ['*.txt'],
124 'IPython.testing.plugin' : ['*.txt'],
125 }
125 }
126
126
127 return package_data
127 return package_data
128
128
129
129
130 def check_package_data(package_data):
130 def check_package_data(package_data):
131 """verify that package_data globs make sense"""
131 """verify that package_data globs make sense"""
132 print("checking package data")
132 print("checking package data")
133 for pkg, data in package_data.items():
133 for pkg, data in package_data.items():
134 pkg_root = pjoin(*pkg.split('.'))
134 pkg_root = pjoin(*pkg.split('.'))
135 for d in data:
135 for d in data:
136 path = pjoin(pkg_root, d)
136 path = pjoin(pkg_root, d)
137 if '*' in path:
137 if '*' in path:
138 assert len(glob(path)) > 0, "No files match pattern %s" % path
138 assert len(glob(path)) > 0, "No files match pattern %s" % path
139 else:
139 else:
140 assert os.path.exists(path), "Missing package data: %s" % path
140 assert os.path.exists(path), "Missing package data: %s" % path
141
141
142
142
143 def check_package_data_first(command):
143 def check_package_data_first(command):
144 """decorator for checking package_data before running a given command
144 """decorator for checking package_data before running a given command
145
145
146 Probably only needs to wrap build_py
146 Probably only needs to wrap build_py
147 """
147 """
148 class DecoratedCommand(command):
148 class DecoratedCommand(command):
149 def run(self):
149 def run(self):
150 check_package_data(self.package_data)
150 check_package_data(self.package_data)
151 command.run(self)
151 command.run(self)
152 return DecoratedCommand
152 return DecoratedCommand
153
153
154
154
155 #---------------------------------------------------------------------------
155 #---------------------------------------------------------------------------
156 # Find data files
156 # Find data files
157 #---------------------------------------------------------------------------
157 #---------------------------------------------------------------------------
158
158
159 def find_data_files():
159 def find_data_files():
160 """
160 """
161 Find IPython's data_files.
161 Find IPython's data_files.
162
162
163 Just man pages at this point.
163 Just man pages at this point.
164 """
164 """
165
165
166 if "freebsd" in sys.platform:
166 if "freebsd" in sys.platform:
167 manpagebase = pjoin('man', 'man1')
167 manpagebase = pjoin('man', 'man1')
168 else:
168 else:
169 manpagebase = pjoin('share', 'man', 'man1')
169 manpagebase = pjoin('share', 'man', 'man1')
170
170
171 # Simple file lists can be made by hand
171 # Simple file lists can be made by hand
172 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
172 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
173 if not manpages:
173 if not manpages:
174 # When running from a source tree, the manpages aren't gzipped
174 # When running from a source tree, the manpages aren't gzipped
175 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
175 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
176
176
177 # And assemble the entire output list
177 # And assemble the entire output list
178 data_files = [ (manpagebase, manpages) ]
178 data_files = [ (manpagebase, manpages) ]
179
179
180 return data_files
180 return data_files
181
181
182
182
183 # The two functions below are copied from IPython.utils.path, so we don't need
183 # The two functions below are copied from IPython.utils.path, so we don't need
184 # to import IPython during setup, which fails on Python 3.
184 # to import IPython during setup, which fails on Python 3.
185
185
186 def target_outdated(target,deps):
186 def target_outdated(target,deps):
187 """Determine whether a target is out of date.
187 """Determine whether a target is out of date.
188
188
189 target_outdated(target,deps) -> 1/0
189 target_outdated(target,deps) -> 1/0
190
190
191 deps: list of filenames which MUST exist.
191 deps: list of filenames which MUST exist.
192 target: single filename which may or may not exist.
192 target: single filename which may or may not exist.
193
193
194 If target doesn't exist or is older than any file listed in deps, return
194 If target doesn't exist or is older than any file listed in deps, return
195 true, otherwise return false.
195 true, otherwise return false.
196 """
196 """
197 try:
197 try:
198 target_time = os.path.getmtime(target)
198 target_time = os.path.getmtime(target)
199 except os.error:
199 except os.error:
200 return 1
200 return 1
201 for dep in deps:
201 for dep in deps:
202 dep_time = os.path.getmtime(dep)
202 dep_time = os.path.getmtime(dep)
203 if dep_time > target_time:
203 if dep_time > target_time:
204 #print "For target",target,"Dep failed:",dep # dbg
204 #print "For target",target,"Dep failed:",dep # dbg
205 #print "times (dep,tar):",dep_time,target_time # dbg
205 #print "times (dep,tar):",dep_time,target_time # dbg
206 return 1
206 return 1
207 return 0
207 return 0
208
208
209
209
210 def target_update(target,deps,cmd):
210 def target_update(target,deps,cmd):
211 """Update a target with a given command given a list of dependencies.
211 """Update a target with a given command given a list of dependencies.
212
212
213 target_update(target,deps,cmd) -> runs cmd if target is outdated.
213 target_update(target,deps,cmd) -> runs cmd if target is outdated.
214
214
215 This is just a wrapper around target_outdated() which calls the given
215 This is just a wrapper around target_outdated() which calls the given
216 command if target is outdated."""
216 command if target is outdated."""
217
217
218 if target_outdated(target,deps):
218 if target_outdated(target,deps):
219 os.system(cmd)
219 os.system(cmd)
220
220
221 #---------------------------------------------------------------------------
221 #---------------------------------------------------------------------------
222 # Find scripts
222 # Find scripts
223 #---------------------------------------------------------------------------
223 #---------------------------------------------------------------------------
224
224
225 def find_entry_points():
225 def find_entry_points():
226 """Defines the command line entry points for IPython
226 """Defines the command line entry points for IPython
227
227
228 This always uses setuptools-style entry points. When setuptools is not in
228 This always uses setuptools-style entry points. When setuptools is not in
229 use, our own build_scripts_entrypt class below parses these and builds
229 use, our own build_scripts_entrypt class below parses these and builds
230 command line scripts.
230 command line scripts.
231
231
232 Each of our entry points gets both a plain name, e.g. ipython, and one
232 Each of our entry points gets both a plain name, e.g. ipython, and one
233 suffixed with the Python major version number, e.g. ipython3.
233 suffixed with the Python major version number, e.g. ipython3.
234 """
234 """
235 ep = [
235 ep = [
236 'ipython%s = IPython:start_ipython',
236 'ipython%s = IPython:start_ipython',
237 'iptest%s = IPython.testing.iptestcontroller:main',
238 ]
237 ]
239 suffix = str(sys.version_info[0])
238 suffix = str(sys.version_info[0])
240 return [e % '' for e in ep] + [e % suffix for e in ep]
239 return [e % '' for e in ep] + [e % suffix for e in ep]
241
240
242 script_src = """#!{executable}
241 script_src = """#!{executable}
243 # This script was automatically generated by setup.py
242 # This script was automatically generated by setup.py
244 if __name__ == '__main__':
243 if __name__ == '__main__':
245 from {mod} import {func}
244 from {mod} import {func}
246 {func}()
245 {func}()
247 """
246 """
248
247
249 class build_scripts_entrypt(build_scripts):
248 class build_scripts_entrypt(build_scripts):
250 """Build the command line scripts
249 """Build the command line scripts
251
250
252 Parse setuptools style entry points and write simple scripts to run the
251 Parse setuptools style entry points and write simple scripts to run the
253 target functions.
252 target functions.
254
253
255 On Windows, this also creates .cmd wrappers for the scripts so that you can
254 On Windows, this also creates .cmd wrappers for the scripts so that you can
256 easily launch them from a command line.
255 easily launch them from a command line.
257 """
256 """
258 def run(self):
257 def run(self):
259 self.mkpath(self.build_dir)
258 self.mkpath(self.build_dir)
260 outfiles = []
259 outfiles = []
261 for script in find_entry_points():
260 for script in find_entry_points():
262 name, entrypt = script.split('=')
261 name, entrypt = script.split('=')
263 name = name.strip()
262 name = name.strip()
264 entrypt = entrypt.strip()
263 entrypt = entrypt.strip()
265 outfile = os.path.join(self.build_dir, name)
264 outfile = os.path.join(self.build_dir, name)
266 outfiles.append(outfile)
265 outfiles.append(outfile)
267 print('Writing script to', outfile)
266 print('Writing script to', outfile)
268
267
269 mod, func = entrypt.split(':')
268 mod, func = entrypt.split(':')
270 with open(outfile, 'w') as f:
269 with open(outfile, 'w') as f:
271 f.write(script_src.format(executable=sys.executable,
270 f.write(script_src.format(executable=sys.executable,
272 mod=mod, func=func))
271 mod=mod, func=func))
273
272
274 if sys.platform == 'win32':
273 if sys.platform == 'win32':
275 # Write .cmd wrappers for Windows so 'ipython' etc. work at the
274 # Write .cmd wrappers for Windows so 'ipython' etc. work at the
276 # command line
275 # command line
277 cmd_file = os.path.join(self.build_dir, name + '.cmd')
276 cmd_file = os.path.join(self.build_dir, name + '.cmd')
278 cmd = r'@"{python}" "%~dp0\{script}" %*\r\n'.format(
277 cmd = r'@"{python}" "%~dp0\{script}" %*\r\n'.format(
279 python=sys.executable, script=name)
278 python=sys.executable, script=name)
280 log.info("Writing %s wrapper script" % cmd_file)
279 log.info("Writing %s wrapper script" % cmd_file)
281 with open(cmd_file, 'w') as f:
280 with open(cmd_file, 'w') as f:
282 f.write(cmd)
281 f.write(cmd)
283
282
284 return outfiles, outfiles
283 return outfiles, outfiles
285
284
286 class install_lib_symlink(Command):
285 class install_lib_symlink(Command):
287 user_options = [
286 user_options = [
288 ('install-dir=', 'd', "directory to install to"),
287 ('install-dir=', 'd', "directory to install to"),
289 ]
288 ]
290
289
291 def initialize_options(self):
290 def initialize_options(self):
292 self.install_dir = None
291 self.install_dir = None
293
292
294 def finalize_options(self):
293 def finalize_options(self):
295 self.set_undefined_options('symlink',
294 self.set_undefined_options('symlink',
296 ('install_lib', 'install_dir'),
295 ('install_lib', 'install_dir'),
297 )
296 )
298
297
299 def run(self):
298 def run(self):
300 if sys.platform == 'win32':
299 if sys.platform == 'win32':
301 raise Exception("This doesn't work on Windows.")
300 raise Exception("This doesn't work on Windows.")
302 pkg = os.path.join(os.getcwd(), 'IPython')
301 pkg = os.path.join(os.getcwd(), 'IPython')
303 dest = os.path.join(self.install_dir, 'IPython')
302 dest = os.path.join(self.install_dir, 'IPython')
304 if os.path.islink(dest):
303 if os.path.islink(dest):
305 print('removing existing symlink at %s' % dest)
304 print('removing existing symlink at %s' % dest)
306 os.unlink(dest)
305 os.unlink(dest)
307 print('symlinking %s -> %s' % (pkg, dest))
306 print('symlinking %s -> %s' % (pkg, dest))
308 os.symlink(pkg, dest)
307 os.symlink(pkg, dest)
309
308
310 class unsymlink(install):
309 class unsymlink(install):
311 def run(self):
310 def run(self):
312 dest = os.path.join(self.install_lib, 'IPython')
311 dest = os.path.join(self.install_lib, 'IPython')
313 if os.path.islink(dest):
312 if os.path.islink(dest):
314 print('removing symlink at %s' % dest)
313 print('removing symlink at %s' % dest)
315 os.unlink(dest)
314 os.unlink(dest)
316 else:
315 else:
317 print('No symlink exists at %s' % dest)
316 print('No symlink exists at %s' % dest)
318
317
319 class install_symlinked(install):
318 class install_symlinked(install):
320 def run(self):
319 def run(self):
321 if sys.platform == 'win32':
320 if sys.platform == 'win32':
322 raise Exception("This doesn't work on Windows.")
321 raise Exception("This doesn't work on Windows.")
323
322
324 # Run all sub-commands (at least those that need to be run)
323 # Run all sub-commands (at least those that need to be run)
325 for cmd_name in self.get_sub_commands():
324 for cmd_name in self.get_sub_commands():
326 self.run_command(cmd_name)
325 self.run_command(cmd_name)
327
326
328 # 'sub_commands': a list of commands this command might have to run to
327 # 'sub_commands': a list of commands this command might have to run to
329 # get its work done. See cmd.py for more info.
328 # get its work done. See cmd.py for more info.
330 sub_commands = [('install_lib_symlink', lambda self:True),
329 sub_commands = [('install_lib_symlink', lambda self:True),
331 ('install_scripts_sym', lambda self:True),
330 ('install_scripts_sym', lambda self:True),
332 ]
331 ]
333
332
334 class install_scripts_for_symlink(install_scripts):
333 class install_scripts_for_symlink(install_scripts):
335 """Redefined to get options from 'symlink' instead of 'install'.
334 """Redefined to get options from 'symlink' instead of 'install'.
336
335
337 I love distutils almost as much as I love setuptools.
336 I love distutils almost as much as I love setuptools.
338 """
337 """
339 def finalize_options(self):
338 def finalize_options(self):
340 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
339 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
341 self.set_undefined_options('symlink',
340 self.set_undefined_options('symlink',
342 ('install_scripts', 'install_dir'),
341 ('install_scripts', 'install_dir'),
343 ('force', 'force'),
342 ('force', 'force'),
344 ('skip_build', 'skip_build'),
343 ('skip_build', 'skip_build'),
345 )
344 )
346
345
347
346
348 #---------------------------------------------------------------------------
347 #---------------------------------------------------------------------------
349 # VCS related
348 # VCS related
350 #---------------------------------------------------------------------------
349 #---------------------------------------------------------------------------
351
350
352
351
353 def git_prebuild(pkg_dir, build_cmd=build_py):
352 def git_prebuild(pkg_dir, build_cmd=build_py):
354 """Return extended build or sdist command class for recording commit
353 """Return extended build or sdist command class for recording commit
355
354
356 records git commit in IPython.utils._sysinfo.commit
355 records git commit in IPython.utils._sysinfo.commit
357
356
358 for use in IPython.utils.sysinfo.sys_info() calls after installation.
357 for use in IPython.utils.sysinfo.sys_info() calls after installation.
359 """
358 """
360
359
361 class MyBuildPy(build_cmd):
360 class MyBuildPy(build_cmd):
362 ''' Subclass to write commit data into installation tree '''
361 ''' Subclass to write commit data into installation tree '''
363 def run(self):
362 def run(self):
364 # loose as `.dev` is suppose to be invalid
363 # loose as `.dev` is suppose to be invalid
365 print("check version number")
364 print("check version number")
366 loose_pep440re = re.compile(r'^(\d+)\.(\d+)\.(\d+((a|b|rc)\d+)?)(\.post\d+)?(\.dev\d*)?$')
365 loose_pep440re = re.compile(r'^(\d+)\.(\d+)\.(\d+((a|b|rc)\d+)?)(\.post\d+)?(\.dev\d*)?$')
367 if not loose_pep440re.match(version):
366 if not loose_pep440re.match(version):
368 raise ValueError("Version number '%s' is not valid (should match [N!]N(.N)*[{a|b|rc}N][.postN][.devN])" % version)
367 raise ValueError("Version number '%s' is not valid (should match [N!]N(.N)*[{a|b|rc}N][.postN][.devN])" % version)
369
368
370
369
371 build_cmd.run(self)
370 build_cmd.run(self)
372 # this one will only fire for build commands
371 # this one will only fire for build commands
373 if hasattr(self, 'build_lib'):
372 if hasattr(self, 'build_lib'):
374 self._record_commit(self.build_lib)
373 self._record_commit(self.build_lib)
375
374
376 def make_release_tree(self, base_dir, files):
375 def make_release_tree(self, base_dir, files):
377 # this one will fire for sdist
376 # this one will fire for sdist
378 build_cmd.make_release_tree(self, base_dir, files)
377 build_cmd.make_release_tree(self, base_dir, files)
379 self._record_commit(base_dir)
378 self._record_commit(base_dir)
380
379
381 def _record_commit(self, base_dir):
380 def _record_commit(self, base_dir):
382 import subprocess
381 import subprocess
383 proc = subprocess.Popen('git rev-parse --short HEAD',
382 proc = subprocess.Popen('git rev-parse --short HEAD',
384 stdout=subprocess.PIPE,
383 stdout=subprocess.PIPE,
385 stderr=subprocess.PIPE,
384 stderr=subprocess.PIPE,
386 shell=True)
385 shell=True)
387 repo_commit, _ = proc.communicate()
386 repo_commit, _ = proc.communicate()
388 repo_commit = repo_commit.strip().decode("ascii")
387 repo_commit = repo_commit.strip().decode("ascii")
389
388
390 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
389 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
391 if os.path.isfile(out_pth) and not repo_commit:
390 if os.path.isfile(out_pth) and not repo_commit:
392 # nothing to write, don't clobber
391 # nothing to write, don't clobber
393 return
392 return
394
393
395 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
394 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
396
395
397 # remove to avoid overwriting original via hard link
396 # remove to avoid overwriting original via hard link
398 try:
397 try:
399 os.remove(out_pth)
398 os.remove(out_pth)
400 except (IOError, OSError):
399 except (IOError, OSError):
401 pass
400 pass
402 with open(out_pth, 'w') as out_file:
401 with open(out_pth, 'w') as out_file:
403 out_file.writelines([
402 out_file.writelines([
404 '# GENERATED BY setup.py\n',
403 '# GENERATED BY setup.py\n',
405 'commit = u"%s"\n' % repo_commit,
404 'commit = u"%s"\n' % repo_commit,
406 ])
405 ])
407 return MyBuildPy
406 return MyBuildPy
408
407
@@ -1,239 +1,239 b''
1 # Simple tool to help for release
1 # Simple tool to help for release
2 # when releasing with bash, simple source it to get asked questions.
2 # when releasing with bash, simple source it to get asked questions.
3
3
4 # misc check before starting
4 # misc check before starting
5
5
6 python -c 'import keyring'
6 python -c 'import keyring'
7 python -c 'import twine'
7 python -c 'import twine'
8 python -c 'import sphinx'
8 python -c 'import sphinx'
9 python -c 'import sphinx_rtd_theme'
9 python -c 'import sphinx_rtd_theme'
10 python -c 'import nose'
10 python -c 'import pytest'
11
11
12
12
13 BLACK=$(tput setaf 1)
13 BLACK=$(tput setaf 1)
14 RED=$(tput setaf 1)
14 RED=$(tput setaf 1)
15 GREEN=$(tput setaf 2)
15 GREEN=$(tput setaf 2)
16 YELLOW=$(tput setaf 3)
16 YELLOW=$(tput setaf 3)
17 BLUE=$(tput setaf 4)
17 BLUE=$(tput setaf 4)
18 MAGENTA=$(tput setaf 5)
18 MAGENTA=$(tput setaf 5)
19 CYAN=$(tput setaf 6)
19 CYAN=$(tput setaf 6)
20 WHITE=$(tput setaf 7)
20 WHITE=$(tput setaf 7)
21 NOR=$(tput sgr0)
21 NOR=$(tput sgr0)
22
22
23
23
24 echo "Will use $EDITOR to edit files when necessary"
24 echo "Will use $EDITOR to edit files when necessary"
25 echo -n "PREV_RELEASE (X.y.z) [$PREV_RELEASE]: "
25 echo -n "PREV_RELEASE (X.y.z) [$PREV_RELEASE]: "
26 read input
26 read input
27 PREV_RELEASE=${input:-$PREV_RELEASE}
27 PREV_RELEASE=${input:-$PREV_RELEASE}
28 echo -n "MILESTONE (X.y) [$MILESTONE]: "
28 echo -n "MILESTONE (X.y) [$MILESTONE]: "
29 read input
29 read input
30 MILESTONE=${input:-$MILESTONE}
30 MILESTONE=${input:-$MILESTONE}
31 echo -n "VERSION (X.y.z) [$VERSION]:"
31 echo -n "VERSION (X.y.z) [$VERSION]:"
32 read input
32 read input
33 VERSION=${input:-$VERSION}
33 VERSION=${input:-$VERSION}
34 echo -n "BRANCH (master|X.y) [$BRANCH]:"
34 echo -n "BRANCH (master|X.y) [$BRANCH]:"
35 read input
35 read input
36 BRANCH=${input:-$BRANCH}
36 BRANCH=${input:-$BRANCH}
37
37
38 ask_section(){
38 ask_section(){
39 echo
39 echo
40 echo $BLUE"$1"$NOR
40 echo $BLUE"$1"$NOR
41 echo -n $GREEN"Press Enter to continue, S to skip: "$NOR
41 echo -n $GREEN"Press Enter to continue, S to skip: "$NOR
42 read -n1 value
42 read -n1 value
43 echo
43 echo
44 if [ -z $value ] || [ $value = 'y' ] ; then
44 if [ -z $value ] || [ $value = 'y' ] ; then
45 return 0
45 return 0
46 fi
46 fi
47 return 1
47 return 1
48 }
48 }
49
49
50
50
51 maybe_edit(){
51 maybe_edit(){
52 echo
52 echo
53 echo $BLUE"$1"$NOR
53 echo $BLUE"$1"$NOR
54 echo -n $GREEN"Press e to Edit $1, any other keys to skip: "$NOR
54 echo -n $GREEN"Press e to Edit $1, any other keys to skip: "$NOR
55 read -n1 value
55 read -n1 value
56 echo
56 echo
57 if [ $value = 'e' ] ; then
57 if [ $value = 'e' ] ; then
58 $EDITOR $1
58 $EDITOR $1
59 fi
59 fi
60 }
60 }
61
61
62
62
63
63
64 echo
64 echo
65 if ask_section "Updating what's new with information from docs/source/whatsnew/pr"
65 if ask_section "Updating what's new with information from docs/source/whatsnew/pr"
66 then
66 then
67 python tools/update_whatsnew.py
67 python tools/update_whatsnew.py
68
68
69 echo
69 echo
70 echo $BLUE"please move the contents of "docs/source/whatsnew/development.rst" to version-X.rst"$NOR
70 echo $BLUE"please move the contents of "docs/source/whatsnew/development.rst" to version-X.rst"$NOR
71 echo $GREEN"Press enter to continue"$NOR
71 echo $GREEN"Press enter to continue"$NOR
72 read
72 read
73 fi
73 fi
74
74
75 if ask_section "Gen Stats, and authors"
75 if ask_section "Gen Stats, and authors"
76 then
76 then
77
77
78 echo
78 echo
79 echo $BLUE"here are all the authors that contributed to this release:"$NOR
79 echo $BLUE"here are all the authors that contributed to this release:"$NOR
80 git log --format="%aN <%aE>" $PREV_RELEASE... | sort -u -f
80 git log --format="%aN <%aE>" $PREV_RELEASE... | sort -u -f
81
81
82 echo
82 echo
83 echo $BLUE"If you see any duplicates cancel (Ctrl-C), then edit .mailmap."
83 echo $BLUE"If you see any duplicates cancel (Ctrl-C), then edit .mailmap."
84 echo $GREEN"Press enter to continue:"$NOR
84 echo $GREEN"Press enter to continue:"$NOR
85 read
85 read
86
86
87 echo $BLUE"generating stats"$NOR
87 echo $BLUE"generating stats"$NOR
88 python tools/github_stats.py --milestone $MILESTONE > stats.rst
88 python tools/github_stats.py --milestone $MILESTONE > stats.rst
89
89
90 echo $BLUE"stats.rst files generated."$NOR
90 echo $BLUE"stats.rst files generated."$NOR
91 echo $GREEN"Please merge it with the right file (github-stats-X.rst) and commit."$NOR
91 echo $GREEN"Please merge it with the right file (github-stats-X.rst) and commit."$NOR
92 echo $GREEN"press enter to continue."$NOR
92 echo $GREEN"press enter to continue."$NOR
93 read
93 read
94
94
95 fi
95 fi
96
96
97 if ask_section "Generate API difference (using frapuccino)"
97 if ask_section "Generate API difference (using frapuccino)"
98 then
98 then
99 echo $BLUE"Checking out $PREV_RELEASE"$NOR
99 echo $BLUE"Checking out $PREV_RELEASE"$NOR
100 git checkout $PREV_RELEASE
100 git checkout $PREV_RELEASE
101 echo $BLUE"Saving API to file $PREV_RELEASE"$NOR
101 echo $BLUE"Saving API to file $PREV_RELEASE"$NOR
102 frappuccino IPython --save IPython-$PREV_RELEASE.json
102 frappuccino IPython --save IPython-$PREV_RELEASE.json
103 echo $BLUE"coming back to $BRANCH"$NOR
103 echo $BLUE"coming back to $BRANCH"$NOR
104 git checkout $BRANCH
104 git checkout $BRANCH
105 echo $BLUE"comparing ..."$NOR
105 echo $BLUE"comparing ..."$NOR
106 frappuccino IPython --compare IPython-$PREV_RELEASE.json
106 frappuccino IPython --compare IPython-$PREV_RELEASE.json
107 echo $GREEN"Use the above guideline to write an API changelog ..."$NOR
107 echo $GREEN"Use the above guideline to write an API changelog ..."$NOR
108 echo $GREEN"Press any keys to continue"$NOR
108 echo $GREEN"Press any keys to continue"$NOR
109 read
109 read
110 fi
110 fi
111
111
112 echo "Cleaning repository"
112 echo "Cleaning repository"
113 git clean -xfdi
113 git clean -xfdi
114
114
115 echo $GREEN"please update version number in ${RED}IPython/core/release.py${NOR} , Do not commit yet – we'll do it later."$NOR
115 echo $GREEN"please update version number in ${RED}IPython/core/release.py${NOR} , Do not commit yet – we'll do it later."$NOR
116 echo $GREEN"I tried ${RED}sed -i bkp -e '/Uncomment/s/^# //g' IPython/core/release.py${NOR}"
116 echo $GREEN"I tried ${RED}sed -i bkp -e '/Uncomment/s/^# //g' IPython/core/release.py${NOR}"
117 sed -i bkp -e '/Uncomment/s/^# //g' IPython/core/release.py
117 sed -i bkp -e '/Uncomment/s/^# //g' IPython/core/release.py
118 rm IPython/core/release.pybkp
118 rm IPython/core/release.pybkp
119 git diff
119 git diff
120 maybe_edit IPython/core/release.py
120 maybe_edit IPython/core/release.py
121
121
122 echo $GREEN"Press enter to continue"$NOR
122 echo $GREEN"Press enter to continue"$NOR
123 read
123 read
124
124
125 if ask_section "Build the documentation ?"
125 if ask_section "Build the documentation ?"
126 then
126 then
127 make html -C docs
127 make html -C docs
128 echo
128 echo
129 echo $GREEN"Check the docs, press enter to continue"$NOR
129 echo $GREEN"Check the docs, press enter to continue"$NOR
130 read
130 read
131
131
132 fi
132 fi
133
133
134 if ask_section "Should we commit, tag, push... etc ? "
134 if ask_section "Should we commit, tag, push... etc ? "
135 then
135 then
136 echo
136 echo
137 echo $BLUE"Let's commit : git commit -am \"release $VERSION\" -S"
137 echo $BLUE"Let's commit : git commit -am \"release $VERSION\" -S"
138 echo $GREEN"Press enter to commit"$NOR
138 echo $GREEN"Press enter to commit"$NOR
139 read
139 read
140 git commit -am "release $VERSION" -S
140 git commit -am "release $VERSION" -S
141
141
142 echo
142 echo
143 echo $BLUE"git push origin \$BRANCH ($BRANCH)?"$NOR
143 echo $BLUE"git push origin \$BRANCH ($BRANCH)?"$NOR
144 echo $GREEN"Make sure you can push"$NOR
144 echo $GREEN"Make sure you can push"$NOR
145 echo $GREEN"Press enter to continue"$NOR
145 echo $GREEN"Press enter to continue"$NOR
146 read
146 read
147 git push origin $BRANCH
147 git push origin $BRANCH
148
148
149 echo
149 echo
150 echo "Let's tag : git tag -am \"release $VERSION\" \"$VERSION\" -s"
150 echo "Let's tag : git tag -am \"release $VERSION\" \"$VERSION\" -s"
151 echo $GREEN"Press enter to tag commit"$NOR
151 echo $GREEN"Press enter to tag commit"$NOR
152 read
152 read
153 git tag -am "release $VERSION" "$VERSION" -s
153 git tag -am "release $VERSION" "$VERSION" -s
154
154
155 echo
155 echo
156 echo $BLUE"And push the tag: git push origin \$VERSION ?"$NOR
156 echo $BLUE"And push the tag: git push origin \$VERSION ?"$NOR
157 echo $GREEN"Press enter to continue"$NOR
157 echo $GREEN"Press enter to continue"$NOR
158 read
158 read
159 git push origin $VERSION
159 git push origin $VERSION
160
160
161
161
162 echo $GREEN"please update version number and back to .dev in ${RED}IPython/core/release.py"
162 echo $GREEN"please update version number and back to .dev in ${RED}IPython/core/release.py"
163 echo $GREEN"I tried ${RED}sed -i bkp -e '/Uncomment/s/^/# /g' IPython/core/release.py${NOR}"
163 echo $GREEN"I tried ${RED}sed -i bkp -e '/Uncomment/s/^/# /g' IPython/core/release.py${NOR}"
164 sed -i bkp -e '/Uncomment/s/^/# /g' IPython/core/release.py
164 sed -i bkp -e '/Uncomment/s/^/# /g' IPython/core/release.py
165 rm IPython/core/release.pybkp
165 rm IPython/core/release.pybkp
166 git diff
166 git diff
167 echo $GREEN"Please bump ${RED}the minor version number${NOR}"
167 echo $GREEN"Please bump ${RED}the minor version number${NOR}"
168 maybe_edit IPython/core/release.py
168 maybe_edit IPython/core/release.py
169 echo ${BLUE}"Do not commit yet – we'll do it later."$NOR
169 echo ${BLUE}"Do not commit yet – we'll do it later."$NOR
170
170
171
171
172 echo $GREEN"Press enter to continue"$NOR
172 echo $GREEN"Press enter to continue"$NOR
173 read
173 read
174
174
175 echo
175 echo
176 echo "Let's commit : "$BLUE"git commit -am \"back to dev\""$NOR
176 echo "Let's commit : "$BLUE"git commit -am \"back to dev\""$NOR
177 echo $GREEN"Press enter to commit"$NOR
177 echo $GREEN"Press enter to commit"$NOR
178 read
178 read
179 git commit -am "back to dev"
179 git commit -am "back to dev"
180
180
181 echo
181 echo
182 echo $BLUE"git push origin \$BRANCH ($BRANCH)?"$NOR
182 echo $BLUE"git push origin \$BRANCH ($BRANCH)?"$NOR
183 echo $GREEN"Press enter to continue"$NOR
183 echo $GREEN"Press enter to continue"$NOR
184 read
184 read
185 git push origin $BRANCH
185 git push origin $BRANCH
186
186
187
187
188 echo
188 echo
189 echo $BLUE"let's : git checkout $VERSION"$NOR
189 echo $BLUE"let's : git checkout $VERSION"$NOR
190 echo $GREEN"Press enter to continue"$NOR
190 echo $GREEN"Press enter to continue"$NOR
191 read
191 read
192 git checkout $VERSION
192 git checkout $VERSION
193 fi
193 fi
194
194
195 if ask_section "Should we build and release ?"
195 if ask_section "Should we build and release ?"
196 then
196 then
197
197
198 echo $BLUE"going to set SOURCE_DATE_EPOCH"$NOR
198 echo $BLUE"going to set SOURCE_DATE_EPOCH"$NOR
199 echo $BLUE'export SOURCE_DATE_EPOCH=$(git show -s --format=%ct HEAD)'$NOR
199 echo $BLUE'export SOURCE_DATE_EPOCH=$(git show -s --format=%ct HEAD)'$NOR
200 echo $GREEN"Press enter to continue"$NOR
200 echo $GREEN"Press enter to continue"$NOR
201 read
201 read
202
202
203 export SOURCE_DATE_EPOCH=$(git show -s --format=%ct HEAD)
203 export SOURCE_DATE_EPOCH=$(git show -s --format=%ct HEAD)
204
204
205 echo $BLUE"SOURCE_DATE_EPOCH set to $SOURCE_DATE_EPOCH"$NOR
205 echo $BLUE"SOURCE_DATE_EPOCH set to $SOURCE_DATE_EPOCH"$NOR
206 echo $GREEN"Press enter to continue"$NOR
206 echo $GREEN"Press enter to continue"$NOR
207 read
207 read
208
208
209
209
210
210
211 echo
211 echo
212 echo $BLUE"Attempting to build package..."$NOR
212 echo $BLUE"Attempting to build package..."$NOR
213
213
214 tools/release
214 tools/release
215
215
216
216
217 echo $RED'$ shasum -a 256 dist/*'
217 echo $RED'$ shasum -a 256 dist/*'
218 shasum -a 256 dist/*
218 shasum -a 256 dist/*
219 echo $NOR
219 echo $NOR
220
220
221 echo $BLUE"We are going to rebuild, node the hash above, and compare them to the rebuild"$NOR
221 echo $BLUE"We are going to rebuild, node the hash above, and compare them to the rebuild"$NOR
222 echo $GREEN"Press enter to continue"$NOR
222 echo $GREEN"Press enter to continue"$NOR
223 read
223 read
224
224
225 echo
225 echo
226 echo $BLUE"Attempting to build package..."$NOR
226 echo $BLUE"Attempting to build package..."$NOR
227
227
228 tools/release
228 tools/release
229
229
230 echo $RED"Check the shasum for SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH"
230 echo $RED"Check the shasum for SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH"
231 echo $RED'$ shasum -a 256 dist/*'
231 echo $RED'$ shasum -a 256 dist/*'
232 shasum -a 256 dist/*
232 shasum -a 256 dist/*
233 echo $NOR
233 echo $NOR
234
234
235 if ask_section "upload packages ?"
235 if ask_section "upload packages ?"
236 then
236 then
237 tools/release upload
237 tools/release upload
238 fi
238 fi
239 fi
239 fi
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now