Show More
@@ -38,11 +38,6 b' jobs:' | |||||
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 |
@@ -76,20 +76,15 b' For more detailed information, see our [GitHub Workflow](https://github.com/ipyt' | |||||
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 |
|
|
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 |
|
|
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 |
|
|
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 | ``` |
@@ -51,7 +51,6 b' 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 |
@@ -5,6 +5,7 b'' | |||||
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 | |
@@ -14,7 +15,6 b' from contextlib import contextmanager' | |||||
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 | |
@@ -317,8 +317,8 b' class TestCompleter(unittest.TestCase):' | |||||
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() | |
@@ -357,8 +357,8 b' class TestCompleter(unittest.TestCase):' | |||||
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() |
@@ -1,8 +1,5 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 |
@@ -1,7 +1,4 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 |
@@ -125,6 +125,9 b' def doctest_run_option_parser_for_posix():' | |||||
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). | |
@@ -132,16 +135,19 b' def doctest_run_option_parser_for_windows():' | |||||
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 | |||
@@ -556,7 +562,6 b' def test_multiprocessing_run():' | |||||
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') | |
@@ -575,7 +580,7 b' def test_multiprocessing_run():' | |||||
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: |
@@ -12,38 +12,9 b'' | |||||
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 |
|
@@ -44,11 +44,6 b' from decorator import decorator' | |||||
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 | #----------------------------------------------------------------------------- | |
@@ -165,11 +160,8 b' def skip_iptest_but_not_pytest(f):' | |||||
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 |
|
|
164 | """Make function raise SkipTest exception if skip_condition is true | |
173 |
|
165 | |||
174 | Parameters |
|
166 | Parameters | |
175 | ---------- |
|
167 | ---------- | |
@@ -188,57 +180,15 b' def skipif(skip_condition, msg=None):' | |||||
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 |
|
|
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 | |
@@ -265,12 +215,7 b' def skip(msg=None):' | |||||
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 | |
@@ -351,8 +296,6 b" 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 |
@@ -19,35 +19,14 b' Limitations:' | |||||
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 | #----------------------------------------------------------------------------- | |
@@ -195,129 +174,6 b' class IPDoctestOutputChecker(doctest.OutputChecker):' | |||||
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 | |
@@ -594,169 +450,3 b' class DocFileCase(doctest.DocFileCase):' | |||||
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 |
|
@@ -14,7 +14,6 b' 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 | |
@@ -98,8 +97,17 b' def teardown_environment():' | |||||
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 | |
@@ -291,7 +299,8 b' class TestRaiseDeprecation(unittest.TestCase):' | |||||
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 |
|
|
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) |
@@ -26,13 +26,9 b' init:' | |||||
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 |
|
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 |
@@ -5,7 +5,6 b' dependencies:' | |||||
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: |
@@ -100,12 +100,11 b' 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:` |
|
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 |
$ |
|
107 | $ pytest | |
109 |
|
108 | |||
110 | .. _devinstall: |
|
109 | .. _devinstall: | |
111 |
|
110 |
@@ -175,7 +175,6 b' extras_require = dict(' | |||||
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", |
@@ -234,7 +234,6 b' def find_entry_points():' | |||||
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] |
@@ -7,7 +7,7 b" 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 |
|
10 | python -c 'import pytest' | |
11 |
|
11 | |||
12 |
|
12 | |||
13 | BLACK=$(tput setaf 1) |
|
13 | BLACK=$(tput setaf 1) |
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