##// END OF EJS Templates
Merge pull request #12666 from Carreau/pytest-yields
Matthias Bussonnier -
r26194:69589881 merge
parent child Browse files
Show More
@@ -1,113 +1,113 b''
1 # http://travis-ci.org/#!/ipython/ipython
1 # http://travis-ci.org/#!/ipython/ipython
2 language: python
2 language: python
3 os: linux
3 os: linux
4
4
5 addons:
5 addons:
6 apt:
6 apt:
7 packages:
7 packages:
8 - graphviz
8 - graphviz
9
9
10 python:
10 python:
11 - 3.8
11 - 3.8
12
12
13 env:
13 env:
14 global:
14 global:
15 - PATH=$TRAVIS_BUILD_DIR/pandoc:$PATH
15 - PATH=$TRAVIS_BUILD_DIR/pandoc:$PATH
16
16
17 group: edge
17 group: edge
18
18
19 before_install:
19 before_install:
20 - |
20 - |
21 # install Python on macOS
21 # install Python on macOS
22 if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
22 if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
23 env | sort
23 env | sort
24 if ! which python$TRAVIS_PYTHON_VERSION; then
24 if ! which python$TRAVIS_PYTHON_VERSION; then
25 HOMEBREW_NO_AUTO_UPDATE=1 brew tap minrk/homebrew-python-frameworks
25 HOMEBREW_NO_AUTO_UPDATE=1 brew tap minrk/homebrew-python-frameworks
26 HOMEBREW_NO_AUTO_UPDATE=1 brew cask install python-framework-${TRAVIS_PYTHON_VERSION/./}
26 HOMEBREW_NO_AUTO_UPDATE=1 brew cask install python-framework-${TRAVIS_PYTHON_VERSION/./}
27 fi
27 fi
28 python3 -m pip install virtualenv
28 python3 -m pip install virtualenv
29 python3 -m virtualenv -p $(which python$TRAVIS_PYTHON_VERSION) ~/travis-env
29 python3 -m virtualenv -p $(which python$TRAVIS_PYTHON_VERSION) ~/travis-env
30 source ~/travis-env/bin/activate
30 source ~/travis-env/bin/activate
31 fi
31 fi
32 - python --version
32 - python --version
33
33
34 install:
34 install:
35 - pip install pip --upgrade
35 - pip install pip --upgrade
36 - pip install setuptools --upgrade
36 - pip install setuptools --upgrade
37 - if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then
37 - if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then
38 echo "for the time being still test on 3.6";
38 echo "for the time being still test on 3.6";
39 sed -ibkp s/7/6/g setup.py;
39 sed -ibkp s/7/6/g setup.py;
40 git diff;
40 git diff;
41 fi
41 fi
42 - pip install -e file://$PWD#egg=ipython[test] --upgrade
42 - pip install -e file://$PWD#egg=ipython[test] --upgrade
43 - pip install trio curio --upgrade --upgrade-strategy eager
43 - pip install trio curio --upgrade --upgrade-strategy eager
44 - pip install 'pytest<6' 'matplotlib !=3.2.0'
44 - pip install 'pytest' 'matplotlib !=3.2.0'
45 - pip install codecov check-manifest pytest-cov --upgrade
45 - pip install codecov check-manifest pytest-cov --upgrade
46
46
47
47
48 script:
48 script:
49 - check-manifest
49 - check-manifest
50 - |
50 - |
51 if [[ "$TRAVIS_PYTHON_VERSION" == "nightly" ]]; then
51 if [[ "$TRAVIS_PYTHON_VERSION" == "nightly" ]]; then
52 # on nightly fake parso known the grammar
52 # on nightly fake parso known the grammar
53 cp /home/travis/virtualenv/python3.9-dev/lib/python3.9/site-packages/parso/python/grammar38.txt /home/travis/virtualenv/python3.9-dev/lib/python3.9/site-packages/parso/python/grammar39.txt
53 cp /home/travis/virtualenv/python3.9-dev/lib/python3.9/site-packages/parso/python/grammar38.txt /home/travis/virtualenv/python3.9-dev/lib/python3.9/site-packages/parso/python/grammar39.txt
54 fi
54 fi
55 - |
55 - |
56 if [[ "$TRAVIS_PYTHON_VERSION" == "3.8" ]] && [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
56 if [[ "$TRAVIS_PYTHON_VERSION" == "3.8" ]] && [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
57 cd /tmp && iptest --coverage xml && cd -
57 cd /tmp && iptest --coverage xml && cd -
58 fi
58 fi
59 - pytest --maxfail=10 IPython
59 - pytest --maxfail=10 IPython
60 # On the latest Python (on Linux) only, make sure that the docs build.
60 # On the latest Python (on Linux) only, make sure that the docs build.
61 - |
61 - |
62 if [[ "$TRAVIS_PYTHON_VERSION" == "3.8" ]] && [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
62 if [[ "$TRAVIS_PYTHON_VERSION" == "3.8" ]] && [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
63 pip install -r docs/requirements.txt
63 pip install -r docs/requirements.txt
64 python tools/fixup_whats_new_pr.py
64 python tools/fixup_whats_new_pr.py
65 make -C docs/ html SPHINXOPTS="-W"
65 make -C docs/ html SPHINXOPTS="-W"
66 fi
66 fi
67
67
68 after_success:
68 after_success:
69 - cp /tmp/ipy_coverage.xml ./
69 - cp /tmp/ipy_coverage.xml ./
70 - cp /tmp/.coverage ./
70 - cp /tmp/.coverage ./
71 - codecov
71 - codecov
72
72
73 matrix:
73 matrix:
74 include:
74 include:
75 - arch: amd64
75 - arch: amd64
76 python: "3.6"
76 python: "3.6"
77 dist: xenial
77 dist: xenial
78 - arch: amd64
78 - arch: amd64
79 python: "3.7"
79 python: "3.7"
80 dist: xenial
80 dist: xenial
81 - arch: amd64
81 - arch: amd64
82 python: "3.8"
82 python: "3.8"
83 dist: xenial
83 dist: xenial
84 - arch: amd64
84 - arch: amd64
85 python: "nightly"
85 python: "nightly"
86 dist: xenial
86 dist: xenial
87 - arch: amd64
87 - arch: amd64
88 python: "3.9-dev"
88 python: "3.9-dev"
89 - os: osx
89 - os: osx
90 language: generic
90 language: generic
91 python: 3.7
91 python: 3.7
92 env: TRAVIS_PYTHON_VERSION=3.7
92 env: TRAVIS_PYTHON_VERSION=3.7
93 allow_failures:
93 allow_failures:
94 - python: nightly
94 - python: nightly
95
95
96 before_deploy:
96 before_deploy:
97 - rm -rf dist/
97 - rm -rf dist/
98 - python setup.py sdist
98 - python setup.py sdist
99 - python setup.py bdist_wheel
99 - python setup.py bdist_wheel
100
100
101 deploy:
101 deploy:
102 provider: releases
102 provider: releases
103 api_key:
103 api_key:
104 secure: Y/Ae9tYs5aoBU8bDjN2YrwGG6tCbezj/h3Lcmtx8HQavSbBgXnhnZVRb2snOKD7auqnqjfT/7QMm4ZyKvaOEgyggGktKqEKYHC8KOZ7yp8I5/UMDtk6j9TnXpSqqBxPiud4MDV76SfRYEQiaDoG4tGGvSfPJ9KcNjKrNvSyyxns=
104 secure: Y/Ae9tYs5aoBU8bDjN2YrwGG6tCbezj/h3Lcmtx8HQavSbBgXnhnZVRb2snOKD7auqnqjfT/7QMm4ZyKvaOEgyggGktKqEKYHC8KOZ7yp8I5/UMDtk6j9TnXpSqqBxPiud4MDV76SfRYEQiaDoG4tGGvSfPJ9KcNjKrNvSyyxns=
105 file: dist/*
105 file: dist/*
106 file_glob: true
106 file_glob: true
107 cleanup: false
107 cleanup: false
108 on:
108 on:
109 repo: ipython/ipython
109 repo: ipython/ipython
110 all_branches: true # Backports are released from e.g. 5.x branch
110 all_branches: true # Backports are released from e.g. 5.x branch
111 tags: true
111 tags: true
112 python: 3.6 # Any version should work, but we only need one
112 python: 3.6 # Any version should work, but we only need one
113 condition: $TRAVIS_OS_NAME = "linux"
113 condition: $TRAVIS_OS_NAME = "linux"
@@ -1,222 +1,222 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tools for handling LaTeX."""
2 """Tools for handling LaTeX."""
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 from io import BytesIO, open
7 from io import BytesIO, open
8 import os
8 import os
9 import tempfile
9 import tempfile
10 import shutil
10 import shutil
11 import subprocess
11 import subprocess
12 from base64 import encodebytes
12 from base64 import encodebytes
13 import textwrap
13 import textwrap
14
14
15 from pathlib import Path, PurePath
15 from pathlib import Path, PurePath
16
16
17 from IPython.utils.process import find_cmd, FindCmdError
17 from IPython.utils.process import find_cmd, FindCmdError
18 from traitlets.config import get_config
18 from traitlets.config import get_config
19 from traitlets.config.configurable import SingletonConfigurable
19 from traitlets.config.configurable import SingletonConfigurable
20 from traitlets import List, Bool, Unicode
20 from traitlets import List, Bool, Unicode
21 from IPython.utils.py3compat import cast_unicode
21 from IPython.utils.py3compat import cast_unicode
22
22
23
23
24 class LaTeXTool(SingletonConfigurable):
24 class LaTeXTool(SingletonConfigurable):
25 """An object to store configuration of the LaTeX tool."""
25 """An object to store configuration of the LaTeX tool."""
26 def _config_default(self):
26 def _config_default(self):
27 return get_config()
27 return get_config()
28
28
29 backends = List(
29 backends = List(
30 Unicode(), ["matplotlib", "dvipng"],
30 Unicode(), ["matplotlib", "dvipng"],
31 help="Preferred backend to draw LaTeX math equations. "
31 help="Preferred backend to draw LaTeX math equations. "
32 "Backends in the list are checked one by one and the first "
32 "Backends in the list are checked one by one and the first "
33 "usable one is used. Note that `matplotlib` backend "
33 "usable one is used. Note that `matplotlib` backend "
34 "is usable only for inline style equations. To draw "
34 "is usable only for inline style equations. To draw "
35 "display style equations, `dvipng` backend must be specified. ",
35 "display style equations, `dvipng` backend must be specified. ",
36 # It is a List instead of Enum, to make configuration more
36 # It is a List instead of Enum, to make configuration more
37 # flexible. For example, to use matplotlib mainly but dvipng
37 # flexible. For example, to use matplotlib mainly but dvipng
38 # for display style, the default ["matplotlib", "dvipng"] can
38 # for display style, the default ["matplotlib", "dvipng"] can
39 # be used. To NOT use dvipng so that other repr such as
39 # be used. To NOT use dvipng so that other repr such as
40 # unicode pretty printing is used, you can use ["matplotlib"].
40 # unicode pretty printing is used, you can use ["matplotlib"].
41 ).tag(config=True)
41 ).tag(config=True)
42
42
43 use_breqn = Bool(
43 use_breqn = Bool(
44 True,
44 True,
45 help="Use breqn.sty to automatically break long equations. "
45 help="Use breqn.sty to automatically break long equations. "
46 "This configuration takes effect only for dvipng backend.",
46 "This configuration takes effect only for dvipng backend.",
47 ).tag(config=True)
47 ).tag(config=True)
48
48
49 packages = List(
49 packages = List(
50 ['amsmath', 'amsthm', 'amssymb', 'bm'],
50 ['amsmath', 'amsthm', 'amssymb', 'bm'],
51 help="A list of packages to use for dvipng backend. "
51 help="A list of packages to use for dvipng backend. "
52 "'breqn' will be automatically appended when use_breqn=True.",
52 "'breqn' will be automatically appended when use_breqn=True.",
53 ).tag(config=True)
53 ).tag(config=True)
54
54
55 preamble = Unicode(
55 preamble = Unicode(
56 help="Additional preamble to use when generating LaTeX source "
56 help="Additional preamble to use when generating LaTeX source "
57 "for dvipng backend.",
57 "for dvipng backend.",
58 ).tag(config=True)
58 ).tag(config=True)
59
59
60
60
61 def latex_to_png(s, encode=False, backend=None, wrap=False, color='Black',
61 def latex_to_png(s, encode=False, backend=None, wrap=False, color='Black',
62 scale=1.0):
62 scale=1.0):
63 """Render a LaTeX string to PNG.
63 """Render a LaTeX string to PNG.
64
64
65 Parameters
65 Parameters
66 ----------
66 ----------
67 s : str
67 s : str
68 The raw string containing valid inline LaTeX.
68 The raw string containing valid inline LaTeX.
69 encode : bool, optional
69 encode : bool, optional
70 Should the PNG data base64 encoded to make it JSON'able.
70 Should the PNG data base64 encoded to make it JSON'able.
71 backend : {matplotlib, dvipng}
71 backend : {matplotlib, dvipng}
72 Backend for producing PNG data.
72 Backend for producing PNG data.
73 wrap : bool
73 wrap : bool
74 If true, Automatically wrap `s` as a LaTeX equation.
74 If true, Automatically wrap `s` as a LaTeX equation.
75 color : string
75 color : string
76 Foreground color name among dvipsnames, e.g. 'Maroon' or on hex RGB
76 Foreground color name among dvipsnames, e.g. 'Maroon' or on hex RGB
77 format, e.g. '#AA20FA'.
77 format, e.g. '#AA20FA'.
78 scale : float
78 scale : float
79 Scale factor for the resulting PNG.
79 Scale factor for the resulting PNG.
80
80
81 None is returned when the backend cannot be used.
81 None is returned when the backend cannot be used.
82
82
83 """
83 """
84 s = cast_unicode(s)
84 s = cast_unicode(s)
85 allowed_backends = LaTeXTool.instance().backends
85 allowed_backends = LaTeXTool.instance().backends
86 if backend is None:
86 if backend is None:
87 backend = allowed_backends[0]
87 backend = allowed_backends[0]
88 if backend not in allowed_backends:
88 if backend not in allowed_backends:
89 return None
89 return None
90 if backend == 'matplotlib':
90 if backend == 'matplotlib':
91 f = latex_to_png_mpl
91 f = latex_to_png_mpl
92 elif backend == 'dvipng':
92 elif backend == 'dvipng':
93 f = latex_to_png_dvipng
93 f = latex_to_png_dvipng
94 if color.startswith('#'):
94 if color.startswith('#'):
95 # Convert hex RGB color to LaTeX RGB color.
95 # Convert hex RGB color to LaTeX RGB color.
96 if len(color) == 7:
96 if len(color) == 7:
97 try:
97 try:
98 color = "RGB {}".format(" ".join([str(int(x, 16)) for x in
98 color = "RGB {}".format(" ".join([str(int(x, 16)) for x in
99 textwrap.wrap(color[1:], 2)]))
99 textwrap.wrap(color[1:], 2)]))
100 except ValueError as e:
100 except ValueError as e:
101 raise ValueError('Invalid color specification {}.'.format(color)) from e
101 raise ValueError('Invalid color specification {}.'.format(color)) from e
102 else:
102 else:
103 raise ValueError('Invalid color specification {}.'.format(color))
103 raise ValueError('Invalid color specification {}.'.format(color))
104 else:
104 else:
105 raise ValueError('No such backend {0}'.format(backend))
105 raise ValueError('No such backend {0}'.format(backend))
106 bin_data = f(s, wrap, color, scale)
106 bin_data = f(s, wrap, color, scale)
107 if encode and bin_data:
107 if encode and bin_data:
108 bin_data = encodebytes(bin_data)
108 bin_data = encodebytes(bin_data)
109 return bin_data
109 return bin_data
110
110
111
111
112 def latex_to_png_mpl(s, wrap, color='Black', scale=1.0):
112 def latex_to_png_mpl(s, wrap, color='Black', scale=1.0):
113 try:
113 try:
114 from matplotlib import mathtext
114 from matplotlib import mathtext
115 from pyparsing import ParseFatalException
115 from pyparsing import ParseFatalException
116 except ImportError:
116 except ImportError:
117 return None
117 return None
118
118
119 # mpl mathtext doesn't support display math, force inline
119 # mpl mathtext doesn't support display math, force inline
120 s = s.replace('$$', '$')
120 s = s.replace('$$', '$')
121 if wrap:
121 if wrap:
122 s = u'${0}$'.format(s)
122 s = u'${0}$'.format(s)
123
123
124 try:
124 try:
125 mt = mathtext.MathTextParser('bitmap')
125 mt = mathtext.MathTextParser('bitmap')
126 f = BytesIO()
126 f = BytesIO()
127 dpi = 120*scale
127 dpi = 120*scale
128 mt.to_png(f, s, fontsize=12, dpi=dpi, color=color)
128 mt.to_png(f, s, fontsize=12, dpi=dpi, color=color)
129 return f.getvalue()
129 return f.getvalue()
130 except (ValueError, RuntimeError, ParseFatalException):
130 except (ValueError, RuntimeError, ParseFatalException):
131 return None
131 return None
132
132
133
133
134 def latex_to_png_dvipng(s, wrap, color='Black', scale=1.0):
134 def latex_to_png_dvipng(s, wrap, color='Black', scale=1.0):
135 try:
135 try:
136 find_cmd('latex')
136 find_cmd('latex')
137 find_cmd('dvipng')
137 find_cmd('dvipng')
138 except FindCmdError:
138 except FindCmdError:
139 return None
139 return None
140 try:
140 try:
141 workdir = PurePath(tempfile.mkdtemp())
141 workdir = Path(tempfile.mkdtemp())
142 tmpfile = workdir.joinpath("tmp.tex")
142 tmpfile = workdir.joinpath("tmp.tex")
143 dvifile = workdir.joinpath("tmp.dvi")
143 dvifile = workdir.joinpath("tmp.dvi")
144 outfile = workdir.joinpath("tmp.png")
144 outfile = workdir.joinpath("tmp.png")
145
145
146 with tmpfile.open("w", encoding="utf8") as f:
146 with tmpfile.open("w", encoding="utf8") as f:
147 f.writelines(genelatex(s, wrap))
147 f.writelines(genelatex(s, wrap))
148
148
149 with open(os.devnull, 'wb') as devnull:
149 with open(os.devnull, 'wb') as devnull:
150 subprocess.check_call(
150 subprocess.check_call(
151 ["latex", "-halt-on-error", "-interaction", "batchmode", tmpfile],
151 ["latex", "-halt-on-error", "-interaction", "batchmode", tmpfile],
152 cwd=workdir, stdout=devnull, stderr=devnull)
152 cwd=workdir, stdout=devnull, stderr=devnull)
153
153
154 resolution = round(150*scale)
154 resolution = round(150*scale)
155 subprocess.check_call(
155 subprocess.check_call(
156 ["dvipng", "-T", "tight", "-D", str(resolution), "-z", "9",
156 ["dvipng", "-T", "tight", "-D", str(resolution), "-z", "9",
157 "-bg", "transparent", "-o", outfile, dvifile, "-fg", color],
157 "-bg", "transparent", "-o", outfile, dvifile, "-fg", color],
158 cwd=workdir, stdout=devnull, stderr=devnull)
158 cwd=workdir, stdout=devnull, stderr=devnull)
159
159
160 with outfile.open("rb") as f:
160 with outfile.open("rb") as f:
161 return f.read()
161 return f.read()
162 except subprocess.CalledProcessError:
162 except subprocess.CalledProcessError:
163 return None
163 return None
164 finally:
164 finally:
165 shutil.rmtree(workdir)
165 shutil.rmtree(workdir)
166
166
167
167
168 def kpsewhich(filename):
168 def kpsewhich(filename):
169 """Invoke kpsewhich command with an argument `filename`."""
169 """Invoke kpsewhich command with an argument `filename`."""
170 try:
170 try:
171 find_cmd("kpsewhich")
171 find_cmd("kpsewhich")
172 proc = subprocess.Popen(
172 proc = subprocess.Popen(
173 ["kpsewhich", filename],
173 ["kpsewhich", filename],
174 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
174 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
175 (stdout, stderr) = proc.communicate()
175 (stdout, stderr) = proc.communicate()
176 return stdout.strip().decode('utf8', 'replace')
176 return stdout.strip().decode('utf8', 'replace')
177 except FindCmdError:
177 except FindCmdError:
178 pass
178 pass
179
179
180
180
181 def genelatex(body, wrap):
181 def genelatex(body, wrap):
182 """Generate LaTeX document for dvipng backend."""
182 """Generate LaTeX document for dvipng backend."""
183 lt = LaTeXTool.instance()
183 lt = LaTeXTool.instance()
184 breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty")
184 breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty")
185 yield r'\documentclass{article}'
185 yield r'\documentclass{article}'
186 packages = lt.packages
186 packages = lt.packages
187 if breqn:
187 if breqn:
188 packages = packages + ['breqn']
188 packages = packages + ['breqn']
189 for pack in packages:
189 for pack in packages:
190 yield r'\usepackage{{{0}}}'.format(pack)
190 yield r'\usepackage{{{0}}}'.format(pack)
191 yield r'\pagestyle{empty}'
191 yield r'\pagestyle{empty}'
192 if lt.preamble:
192 if lt.preamble:
193 yield lt.preamble
193 yield lt.preamble
194 yield r'\begin{document}'
194 yield r'\begin{document}'
195 if breqn:
195 if breqn:
196 yield r'\begin{dmath*}'
196 yield r'\begin{dmath*}'
197 yield body
197 yield body
198 yield r'\end{dmath*}'
198 yield r'\end{dmath*}'
199 elif wrap:
199 elif wrap:
200 yield u'$${0}$$'.format(body)
200 yield u'$${0}$$'.format(body)
201 else:
201 else:
202 yield body
202 yield body
203 yield u'\\end{document}'
203 yield u'\\end{document}'
204
204
205
205
206 _data_uri_template_png = u"""<img src="data:image/png;base64,%s" alt=%s />"""
206 _data_uri_template_png = u"""<img src="data:image/png;base64,%s" alt=%s />"""
207
207
208 def latex_to_html(s, alt='image'):
208 def latex_to_html(s, alt='image'):
209 """Render LaTeX to HTML with embedded PNG data using data URIs.
209 """Render LaTeX to HTML with embedded PNG data using data URIs.
210
210
211 Parameters
211 Parameters
212 ----------
212 ----------
213 s : str
213 s : str
214 The raw string containing valid inline LateX.
214 The raw string containing valid inline LateX.
215 alt : str
215 alt : str
216 The alt text to use for the HTML.
216 The alt text to use for the HTML.
217 """
217 """
218 base64_data = latex_to_png(s, encode=True).decode('ascii')
218 base64_data = latex_to_png(s, encode=True).decode('ascii')
219 if base64_data:
219 if base64_data:
220 return _data_uri_template_png % (base64_data, alt)
220 return _data_uri_template_png % (base64_data, alt)
221
221
222
222
@@ -1,183 +1,191 b''
1 """Tests for IPython.utils.path.py"""
1 """Tests for IPython.utils.path.py"""
2 # Copyright (c) IPython Development Team.
2 # Copyright (c) IPython Development Team.
3 # Distributed under the terms of the Modified BSD License.
3 # Distributed under the terms of the Modified BSD License.
4
4
5 from contextlib import contextmanager
5 from contextlib import contextmanager
6 from unittest.mock import patch
6 from unittest.mock import patch
7
7
8 import nose.tools as nt
8 import nose.tools as nt
9 import pytest
9 import pytest
10
10
11 from IPython.lib import latextools
11 from IPython.lib import latextools
12 from IPython.testing.decorators import onlyif_cmds_exist, skipif_not_matplotlib
12 from IPython.testing.decorators import (
13 onlyif_cmds_exist,
14 skipif_not_matplotlib,
15 skip_iptest_but_not_pytest,
16 )
13 from IPython.utils.process import FindCmdError
17 from IPython.utils.process import FindCmdError
14
18
15
19
16 @pytest.mark.parametrize('command', ['latex', 'dvipng'])
20 @pytest.mark.parametrize('command', ['latex', 'dvipng'])
21 @skip_iptest_but_not_pytest
17 def test_check_latex_to_png_dvipng_fails_when_no_cmd(command):
22 def test_check_latex_to_png_dvipng_fails_when_no_cmd(command):
18 def mock_find_cmd(arg):
23 def mock_find_cmd(arg):
19 if arg == command:
24 if arg == command:
20 raise FindCmdError
25 raise FindCmdError
21
26
22 with patch.object(latextools, "find_cmd", mock_find_cmd):
27 with patch.object(latextools, "find_cmd", mock_find_cmd):
23 assert latextools.latex_to_png_dvipng("whatever", True) == None
28 assert latextools.latex_to_png_dvipng("whatever", True) == None
24
29
25
30
26 @onlyif_cmds_exist('latex', 'dvipng')
31 @contextmanager
27 def test_latex_to_png_dvipng_runs():
32 def no_op(*args, **kwargs):
33 yield
34
35
36 @skip_iptest_but_not_pytest
37 @onlyif_cmds_exist("latex", "dvipng")
38 @pytest.mark.parametrize("s, wrap", [(u"$$x^2$$", False), (u"x^2", True)])
39 def test_latex_to_png_dvipng_runs(s, wrap):
28 """
40 """
29 Test that latex_to_png_dvipng just runs without error.
41 Test that latex_to_png_dvipng just runs without error.
30 """
42 """
31 def mock_kpsewhich(filename):
43 def mock_kpsewhich(filename):
32 assert filename == "breqn.sty"
44 assert filename == "breqn.sty"
33 return None
45 return None
34
46
35 for (s, wrap) in [(u"$$x^2$$", False), (u"x^2", True)]:
47 latextools.latex_to_png_dvipng(s, wrap)
36 yield (latextools.latex_to_png_dvipng, s, wrap)
37
48
38 with patch.object(latextools, "kpsewhich", mock_kpsewhich):
49 with patch_latextool(mock_kpsewhich):
39 yield (latextools.latex_to_png_dvipng, s, wrap)
50 latextools.latex_to_png_dvipng(s, wrap)
40
51
41
52
42 @contextmanager
43 def no_op(*args, **kwargs):
44 yield
45
46 def mock_kpsewhich(filename):
53 def mock_kpsewhich(filename):
47 assert filename == "breqn.sty"
54 assert filename == "breqn.sty"
48 return None
55 return None
49
56
50 @contextmanager
57 @contextmanager
51 def patch_latextool():
58 def patch_latextool(mock=mock_kpsewhich):
52 with patch.object(latextools, "kpsewhich", mock_kpsewhich):
59 with patch.object(latextools, "kpsewhich", mock):
53 yield
60 yield
54
61
55 @pytest.mark.parametrize('context', [no_op, patch_latextool])
62 @pytest.mark.parametrize('context', [no_op, patch_latextool])
56 @pytest.mark.parametrize('s_wrap', [("$x^2$", False), ("x^2", True)])
63 @pytest.mark.parametrize('s_wrap', [("$x^2$", False), ("x^2", True)])
64 @skip_iptest_but_not_pytest
57 def test_latex_to_png_mpl_runs(s_wrap, context):
65 def test_latex_to_png_mpl_runs(s_wrap, context):
58 """
66 """
59 Test that latex_to_png_mpl just runs without error.
67 Test that latex_to_png_mpl just runs without error.
60 """
68 """
61 try:
69 try:
62 import matplotbli
70 import matplotlib
63 except ImportError:
71 except ImportError:
64 pytest.skip("This needs matplotlib to be availlable")
72 pytest.skip("This needs matplotlib to be available")
65 return
73 return
66 s, wrap = s_wrap
74 s, wrap = s_wrap
67 with context():
75 with context():
68 latextools.latex_to_png_mpl(s, wrap)
76 latextools.latex_to_png_mpl(s, wrap)
69
77
70 @skipif_not_matplotlib
78 @skipif_not_matplotlib
71 def test_latex_to_html():
79 def test_latex_to_html():
72 img = latextools.latex_to_html("$x^2$")
80 img = latextools.latex_to_html("$x^2$")
73 assert "data:image/png;base64,iVBOR" in img
81 assert "data:image/png;base64,iVBOR" in img
74
82
75
83
76 def test_genelatex_no_wrap():
84 def test_genelatex_no_wrap():
77 """
85 """
78 Test genelatex with wrap=False.
86 Test genelatex with wrap=False.
79 """
87 """
80 def mock_kpsewhich(filename):
88 def mock_kpsewhich(filename):
81 assert False, ("kpsewhich should not be called "
89 assert False, ("kpsewhich should not be called "
82 "(called with {0})".format(filename))
90 "(called with {0})".format(filename))
83
91
84 with patch.object(latextools, "kpsewhich", mock_kpsewhich):
92 with patch_latextool(mock_kpsewhich):
85 assert '\n'.join(latextools.genelatex("body text", False)) == r'''\documentclass{article}
93 assert '\n'.join(latextools.genelatex("body text", False)) == r'''\documentclass{article}
86 \usepackage{amsmath}
94 \usepackage{amsmath}
87 \usepackage{amsthm}
95 \usepackage{amsthm}
88 \usepackage{amssymb}
96 \usepackage{amssymb}
89 \usepackage{bm}
97 \usepackage{bm}
90 \pagestyle{empty}
98 \pagestyle{empty}
91 \begin{document}
99 \begin{document}
92 body text
100 body text
93 \end{document}'''
101 \end{document}'''
94
102
95
103
96 def test_genelatex_wrap_with_breqn():
104 def test_genelatex_wrap_with_breqn():
97 """
105 """
98 Test genelatex with wrap=True for the case breqn.sty is installed.
106 Test genelatex with wrap=True for the case breqn.sty is installed.
99 """
107 """
100 def mock_kpsewhich(filename):
108 def mock_kpsewhich(filename):
101 assert filename == "breqn.sty"
109 assert filename == "breqn.sty"
102 return "path/to/breqn.sty"
110 return "path/to/breqn.sty"
103
111
104 with patch.object(latextools, "kpsewhich", mock_kpsewhich):
112 with patch_latextool(mock_kpsewhich):
105 assert '\n'.join(latextools.genelatex("x^2", True)) == r'''\documentclass{article}
113 assert '\n'.join(latextools.genelatex("x^2", True)) == r'''\documentclass{article}
106 \usepackage{amsmath}
114 \usepackage{amsmath}
107 \usepackage{amsthm}
115 \usepackage{amsthm}
108 \usepackage{amssymb}
116 \usepackage{amssymb}
109 \usepackage{bm}
117 \usepackage{bm}
110 \usepackage{breqn}
118 \usepackage{breqn}
111 \pagestyle{empty}
119 \pagestyle{empty}
112 \begin{document}
120 \begin{document}
113 \begin{dmath*}
121 \begin{dmath*}
114 x^2
122 x^2
115 \end{dmath*}
123 \end{dmath*}
116 \end{document}'''
124 \end{document}'''
117
125
118
126
119 def test_genelatex_wrap_without_breqn():
127 def test_genelatex_wrap_without_breqn():
120 """
128 """
121 Test genelatex with wrap=True for the case breqn.sty is not installed.
129 Test genelatex with wrap=True for the case breqn.sty is not installed.
122 """
130 """
123 def mock_kpsewhich(filename):
131 def mock_kpsewhich(filename):
124 assert filename == "breqn.sty"
132 assert filename == "breqn.sty"
125 return None
133 return None
126
134
127 with patch.object(latextools, "kpsewhich", mock_kpsewhich):
135 with patch_latextool(mock_kpsewhich):
128 assert '\n'.join(latextools.genelatex("x^2", True)) == r'''\documentclass{article}
136 assert '\n'.join(latextools.genelatex("x^2", True)) == r'''\documentclass{article}
129 \usepackage{amsmath}
137 \usepackage{amsmath}
130 \usepackage{amsthm}
138 \usepackage{amsthm}
131 \usepackage{amssymb}
139 \usepackage{amssymb}
132 \usepackage{bm}
140 \usepackage{bm}
133 \pagestyle{empty}
141 \pagestyle{empty}
134 \begin{document}
142 \begin{document}
135 $$x^2$$
143 $$x^2$$
136 \end{document}'''
144 \end{document}'''
137
145
138
146
139 @skipif_not_matplotlib
147 @skipif_not_matplotlib
140 @onlyif_cmds_exist('latex', 'dvipng')
148 @onlyif_cmds_exist('latex', 'dvipng')
141 def test_latex_to_png_color():
149 def test_latex_to_png_color():
142 """
150 """
143 Test color settings for latex_to_png.
151 Test color settings for latex_to_png.
144 """
152 """
145 latex_string = "$x^2$"
153 latex_string = "$x^2$"
146 default_value = latextools.latex_to_png(latex_string, wrap=False)
154 default_value = latextools.latex_to_png(latex_string, wrap=False)
147 default_hexblack = latextools.latex_to_png(latex_string, wrap=False,
155 default_hexblack = latextools.latex_to_png(latex_string, wrap=False,
148 color='#000000')
156 color='#000000')
149 dvipng_default = latextools.latex_to_png_dvipng(latex_string, False)
157 dvipng_default = latextools.latex_to_png_dvipng(latex_string, False)
150 dvipng_black = latextools.latex_to_png_dvipng(latex_string, False, 'Black')
158 dvipng_black = latextools.latex_to_png_dvipng(latex_string, False, 'Black')
151 assert dvipng_default == dvipng_black
159 assert dvipng_default == dvipng_black
152 mpl_default = latextools.latex_to_png_mpl(latex_string, False)
160 mpl_default = latextools.latex_to_png_mpl(latex_string, False)
153 mpl_black = latextools.latex_to_png_mpl(latex_string, False, 'Black')
161 mpl_black = latextools.latex_to_png_mpl(latex_string, False, 'Black')
154 assert mpl_default == mpl_black
162 assert mpl_default == mpl_black
155 assert default_value in [dvipng_black, mpl_black]
163 assert default_value in [dvipng_black, mpl_black]
156 assert default_hexblack in [dvipng_black, mpl_black]
164 assert default_hexblack in [dvipng_black, mpl_black]
157
165
158 # Test that dvips name colors can be used without error
166 # Test that dvips name colors can be used without error
159 dvipng_maroon = latextools.latex_to_png_dvipng(latex_string, False,
167 dvipng_maroon = latextools.latex_to_png_dvipng(latex_string, False,
160 'Maroon')
168 'Maroon')
161 # And that it doesn't return the black one
169 # And that it doesn't return the black one
162 assert dvipng_black != dvipng_maroon
170 assert dvipng_black != dvipng_maroon
163
171
164 mpl_maroon = latextools.latex_to_png_mpl(latex_string, False, 'Maroon')
172 mpl_maroon = latextools.latex_to_png_mpl(latex_string, False, 'Maroon')
165 assert mpl_black != mpl_maroon
173 assert mpl_black != mpl_maroon
166 mpl_white = latextools.latex_to_png_mpl(latex_string, False, 'White')
174 mpl_white = latextools.latex_to_png_mpl(latex_string, False, 'White')
167 mpl_hexwhite = latextools.latex_to_png_mpl(latex_string, False, '#FFFFFF')
175 mpl_hexwhite = latextools.latex_to_png_mpl(latex_string, False, '#FFFFFF')
168 assert mpl_white == mpl_hexwhite
176 assert mpl_white == mpl_hexwhite
169
177
170 mpl_white_scale = latextools.latex_to_png_mpl(latex_string, False,
178 mpl_white_scale = latextools.latex_to_png_mpl(latex_string, False,
171 'White', 1.2)
179 'White', 1.2)
172 assert mpl_white != mpl_white_scale
180 assert mpl_white != mpl_white_scale
173
181
174
182
175 def test_latex_to_png_invalid_hex_colors():
183 def test_latex_to_png_invalid_hex_colors():
176 """
184 """
177 Test that invalid hex colors provided to dvipng gives an exception.
185 Test that invalid hex colors provided to dvipng gives an exception.
178 """
186 """
179 latex_string = "$x^2$"
187 latex_string = "$x^2$"
180 nt.assert_raises(ValueError, lambda: latextools.latex_to_png(latex_string,
188 nt.assert_raises(ValueError, lambda: latextools.latex_to_png(latex_string,
181 backend='dvipng', color="#f00bar"))
189 backend='dvipng', color="#f00bar"))
182 nt.assert_raises(ValueError, lambda: latextools.latex_to_png(latex_string,
190 nt.assert_raises(ValueError, lambda: latextools.latex_to_png(latex_string,
183 backend='dvipng', color="#f00"))
191 backend='dvipng', color="#f00"))
@@ -1,472 +1,492 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Tests for IPython.lib.pretty."""
2 """Tests for IPython.lib.pretty."""
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
7
8 from collections import Counter, defaultdict, deque, OrderedDict
8 from collections import Counter, defaultdict, deque, OrderedDict
9 import os
9 import os
10 import types
10 import types
11 import string
11 import string
12 import unittest
12 import unittest
13
13
14 import nose.tools as nt
14 import nose.tools as nt
15 import pytest
15
16
16 from IPython.lib import pretty
17 from IPython.lib import pretty
17 from IPython.testing.decorators import skip_without
18 from IPython.testing.decorators import skip_without, skip_iptest_but_not_pytest
18
19
19 from io import StringIO
20 from io import StringIO
20
21
21
22
22 class MyList(object):
23 class MyList(object):
23 def __init__(self, content):
24 def __init__(self, content):
24 self.content = content
25 self.content = content
25 def _repr_pretty_(self, p, cycle):
26 def _repr_pretty_(self, p, cycle):
26 if cycle:
27 if cycle:
27 p.text("MyList(...)")
28 p.text("MyList(...)")
28 else:
29 else:
29 with p.group(3, "MyList(", ")"):
30 with p.group(3, "MyList(", ")"):
30 for (i, child) in enumerate(self.content):
31 for (i, child) in enumerate(self.content):
31 if i:
32 if i:
32 p.text(",")
33 p.text(",")
33 p.breakable()
34 p.breakable()
34 else:
35 else:
35 p.breakable("")
36 p.breakable("")
36 p.pretty(child)
37 p.pretty(child)
37
38
38
39
39 class MyDict(dict):
40 class MyDict(dict):
40 def _repr_pretty_(self, p, cycle):
41 def _repr_pretty_(self, p, cycle):
41 p.text("MyDict(...)")
42 p.text("MyDict(...)")
42
43
43 class MyObj(object):
44 class MyObj(object):
44 def somemethod(self):
45 def somemethod(self):
45 pass
46 pass
46
47
47
48
48 class Dummy1(object):
49 class Dummy1(object):
49 def _repr_pretty_(self, p, cycle):
50 def _repr_pretty_(self, p, cycle):
50 p.text("Dummy1(...)")
51 p.text("Dummy1(...)")
51
52
52 class Dummy2(Dummy1):
53 class Dummy2(Dummy1):
53 _repr_pretty_ = None
54 _repr_pretty_ = None
54
55
55 class NoModule(object):
56 class NoModule(object):
56 pass
57 pass
57
58
58 NoModule.__module__ = None
59 NoModule.__module__ = None
59
60
60 class Breaking(object):
61 class Breaking(object):
61 def _repr_pretty_(self, p, cycle):
62 def _repr_pretty_(self, p, cycle):
62 with p.group(4,"TG: ",":"):
63 with p.group(4,"TG: ",":"):
63 p.text("Breaking(")
64 p.text("Breaking(")
64 p.break_()
65 p.break_()
65 p.text(")")
66 p.text(")")
66
67
67 class BreakingRepr(object):
68 class BreakingRepr(object):
68 def __repr__(self):
69 def __repr__(self):
69 return "Breaking(\n)"
70 return "Breaking(\n)"
70
71
71 class BadRepr(object):
72 class BadRepr(object):
72
73
73 def __repr__(self):
74 def __repr__(self):
74 return 1/0
75 return 1/0
75
76
76
77
77 def test_indentation():
78 def test_indentation():
78 """Test correct indentation in groups"""
79 """Test correct indentation in groups"""
79 count = 40
80 count = 40
80 gotoutput = pretty.pretty(MyList(range(count)))
81 gotoutput = pretty.pretty(MyList(range(count)))
81 expectedoutput = "MyList(\n" + ",\n".join(" %d" % i for i in range(count)) + ")"
82 expectedoutput = "MyList(\n" + ",\n".join(" %d" % i for i in range(count)) + ")"
82
83
83 nt.assert_equal(gotoutput, expectedoutput)
84 nt.assert_equal(gotoutput, expectedoutput)
84
85
85
86
86 def test_dispatch():
87 def test_dispatch():
87 """
88 """
88 Test correct dispatching: The _repr_pretty_ method for MyDict
89 Test correct dispatching: The _repr_pretty_ method for MyDict
89 must be found before the registered printer for dict.
90 must be found before the registered printer for dict.
90 """
91 """
91 gotoutput = pretty.pretty(MyDict())
92 gotoutput = pretty.pretty(MyDict())
92 expectedoutput = "MyDict(...)"
93 expectedoutput = "MyDict(...)"
93
94
94 nt.assert_equal(gotoutput, expectedoutput)
95 nt.assert_equal(gotoutput, expectedoutput)
95
96
96
97
97 def test_callability_checking():
98 def test_callability_checking():
98 """
99 """
99 Test that the _repr_pretty_ method is tested for callability and skipped if
100 Test that the _repr_pretty_ method is tested for callability and skipped if
100 not.
101 not.
101 """
102 """
102 gotoutput = pretty.pretty(Dummy2())
103 gotoutput = pretty.pretty(Dummy2())
103 expectedoutput = "Dummy1(...)"
104 expectedoutput = "Dummy1(...)"
104
105
105 nt.assert_equal(gotoutput, expectedoutput)
106 nt.assert_equal(gotoutput, expectedoutput)
106
107
107
108
108 def test_sets():
109 @pytest.mark.parametrize(
110 "obj,expected_output",
111 zip(
112 [
113 set(),
114 frozenset(),
115 set([1]),
116 frozenset([1]),
117 set([1, 2]),
118 frozenset([1, 2]),
119 set([-1, -2, -3]),
120 ],
121 [
122 "set()",
123 "frozenset()",
124 "{1}",
125 "frozenset({1})",
126 "{1, 2}",
127 "frozenset({1, 2})",
128 "{-3, -2, -1}",
129 ],
130 ),
131 )
132 @skip_iptest_but_not_pytest
133 def test_sets(obj, expected_output):
109 """
134 """
110 Test that set and frozenset use Python 3 formatting.
135 Test that set and frozenset use Python 3 formatting.
111 """
136 """
112 objects = [set(), frozenset(), set([1]), frozenset([1]), set([1, 2]),
137 got_output = pretty.pretty(obj)
113 frozenset([1, 2]), set([-1, -2, -3])]
138 nt.assert_equal(got_output, expected_output)
114 expected = ['set()', 'frozenset()', '{1}', 'frozenset({1})', '{1, 2}',
115 'frozenset({1, 2})', '{-3, -2, -1}']
116 for obj, expected_output in zip(objects, expected):
117 got_output = pretty.pretty(obj)
118 yield nt.assert_equal, got_output, expected_output
119
139
120
140
121 @skip_without('xxlimited')
141 @skip_without('xxlimited')
122 def test_pprint_heap_allocated_type():
142 def test_pprint_heap_allocated_type():
123 """
143 """
124 Test that pprint works for heap allocated types.
144 Test that pprint works for heap allocated types.
125 """
145 """
126 import xxlimited
146 import xxlimited
127 output = pretty.pretty(xxlimited.Null)
147 output = pretty.pretty(xxlimited.Null)
128 nt.assert_equal(output, 'xxlimited.Null')
148 nt.assert_equal(output, 'xxlimited.Null')
129
149
130 def test_pprint_nomod():
150 def test_pprint_nomod():
131 """
151 """
132 Test that pprint works for classes with no __module__.
152 Test that pprint works for classes with no __module__.
133 """
153 """
134 output = pretty.pretty(NoModule)
154 output = pretty.pretty(NoModule)
135 nt.assert_equal(output, 'NoModule')
155 nt.assert_equal(output, 'NoModule')
136
156
137 def test_pprint_break():
157 def test_pprint_break():
138 """
158 """
139 Test that p.break_ produces expected output
159 Test that p.break_ produces expected output
140 """
160 """
141 output = pretty.pretty(Breaking())
161 output = pretty.pretty(Breaking())
142 expected = "TG: Breaking(\n ):"
162 expected = "TG: Breaking(\n ):"
143 nt.assert_equal(output, expected)
163 nt.assert_equal(output, expected)
144
164
145 def test_pprint_break_repr():
165 def test_pprint_break_repr():
146 """
166 """
147 Test that p.break_ is used in repr
167 Test that p.break_ is used in repr
148 """
168 """
149 output = pretty.pretty([[BreakingRepr()]])
169 output = pretty.pretty([[BreakingRepr()]])
150 expected = "[[Breaking(\n )]]"
170 expected = "[[Breaking(\n )]]"
151 nt.assert_equal(output, expected)
171 nt.assert_equal(output, expected)
152
172
153 output = pretty.pretty([[BreakingRepr()]*2])
173 output = pretty.pretty([[BreakingRepr()]*2])
154 expected = "[[Breaking(\n ),\n Breaking(\n )]]"
174 expected = "[[Breaking(\n ),\n Breaking(\n )]]"
155 nt.assert_equal(output, expected)
175 nt.assert_equal(output, expected)
156
176
157 def test_bad_repr():
177 def test_bad_repr():
158 """Don't catch bad repr errors"""
178 """Don't catch bad repr errors"""
159 with nt.assert_raises(ZeroDivisionError):
179 with nt.assert_raises(ZeroDivisionError):
160 pretty.pretty(BadRepr())
180 pretty.pretty(BadRepr())
161
181
162 class BadException(Exception):
182 class BadException(Exception):
163 def __str__(self):
183 def __str__(self):
164 return -1
184 return -1
165
185
166 class ReallyBadRepr(object):
186 class ReallyBadRepr(object):
167 __module__ = 1
187 __module__ = 1
168 @property
188 @property
169 def __class__(self):
189 def __class__(self):
170 raise ValueError("I am horrible")
190 raise ValueError("I am horrible")
171
191
172 def __repr__(self):
192 def __repr__(self):
173 raise BadException()
193 raise BadException()
174
194
175 def test_really_bad_repr():
195 def test_really_bad_repr():
176 with nt.assert_raises(BadException):
196 with nt.assert_raises(BadException):
177 pretty.pretty(ReallyBadRepr())
197 pretty.pretty(ReallyBadRepr())
178
198
179
199
180 class SA(object):
200 class SA(object):
181 pass
201 pass
182
202
183 class SB(SA):
203 class SB(SA):
184 pass
204 pass
185
205
186 class TestsPretty(unittest.TestCase):
206 class TestsPretty(unittest.TestCase):
187
207
188 def test_super_repr(self):
208 def test_super_repr(self):
189 # "<super: module_name.SA, None>"
209 # "<super: module_name.SA, None>"
190 output = pretty.pretty(super(SA))
210 output = pretty.pretty(super(SA))
191 self.assertRegex(output, r"<super: \S+.SA, None>")
211 self.assertRegex(output, r"<super: \S+.SA, None>")
192
212
193 # "<super: module_name.SA, <module_name.SB at 0x...>>"
213 # "<super: module_name.SA, <module_name.SB at 0x...>>"
194 sb = SB()
214 sb = SB()
195 output = pretty.pretty(super(SA, sb))
215 output = pretty.pretty(super(SA, sb))
196 self.assertRegex(output, r"<super: \S+.SA,\s+<\S+.SB at 0x\S+>>")
216 self.assertRegex(output, r"<super: \S+.SA,\s+<\S+.SB at 0x\S+>>")
197
217
198
218
199 def test_long_list(self):
219 def test_long_list(self):
200 lis = list(range(10000))
220 lis = list(range(10000))
201 p = pretty.pretty(lis)
221 p = pretty.pretty(lis)
202 last2 = p.rsplit('\n', 2)[-2:]
222 last2 = p.rsplit('\n', 2)[-2:]
203 self.assertEqual(last2, [' 999,', ' ...]'])
223 self.assertEqual(last2, [' 999,', ' ...]'])
204
224
205 def test_long_set(self):
225 def test_long_set(self):
206 s = set(range(10000))
226 s = set(range(10000))
207 p = pretty.pretty(s)
227 p = pretty.pretty(s)
208 last2 = p.rsplit('\n', 2)[-2:]
228 last2 = p.rsplit('\n', 2)[-2:]
209 self.assertEqual(last2, [' 999,', ' ...}'])
229 self.assertEqual(last2, [' 999,', ' ...}'])
210
230
211 def test_long_tuple(self):
231 def test_long_tuple(self):
212 tup = tuple(range(10000))
232 tup = tuple(range(10000))
213 p = pretty.pretty(tup)
233 p = pretty.pretty(tup)
214 last2 = p.rsplit('\n', 2)[-2:]
234 last2 = p.rsplit('\n', 2)[-2:]
215 self.assertEqual(last2, [' 999,', ' ...)'])
235 self.assertEqual(last2, [' 999,', ' ...)'])
216
236
217 def test_long_dict(self):
237 def test_long_dict(self):
218 d = { n:n for n in range(10000) }
238 d = { n:n for n in range(10000) }
219 p = pretty.pretty(d)
239 p = pretty.pretty(d)
220 last2 = p.rsplit('\n', 2)[-2:]
240 last2 = p.rsplit('\n', 2)[-2:]
221 self.assertEqual(last2, [' 999: 999,', ' ...}'])
241 self.assertEqual(last2, [' 999: 999,', ' ...}'])
222
242
223 def test_unbound_method(self):
243 def test_unbound_method(self):
224 output = pretty.pretty(MyObj.somemethod)
244 output = pretty.pretty(MyObj.somemethod)
225 self.assertIn('MyObj.somemethod', output)
245 self.assertIn('MyObj.somemethod', output)
226
246
227
247
228 class MetaClass(type):
248 class MetaClass(type):
229 def __new__(cls, name):
249 def __new__(cls, name):
230 return type.__new__(cls, name, (object,), {'name': name})
250 return type.__new__(cls, name, (object,), {'name': name})
231
251
232 def __repr__(self):
252 def __repr__(self):
233 return "[CUSTOM REPR FOR CLASS %s]" % self.name
253 return "[CUSTOM REPR FOR CLASS %s]" % self.name
234
254
235
255
236 ClassWithMeta = MetaClass('ClassWithMeta')
256 ClassWithMeta = MetaClass('ClassWithMeta')
237
257
238
258
239 def test_metaclass_repr():
259 def test_metaclass_repr():
240 output = pretty.pretty(ClassWithMeta)
260 output = pretty.pretty(ClassWithMeta)
241 nt.assert_equal(output, "[CUSTOM REPR FOR CLASS ClassWithMeta]")
261 nt.assert_equal(output, "[CUSTOM REPR FOR CLASS ClassWithMeta]")
242
262
243
263
244 def test_unicode_repr():
264 def test_unicode_repr():
245 u = u"üniçodé"
265 u = u"üniçodé"
246 ustr = u
266 ustr = u
247
267
248 class C(object):
268 class C(object):
249 def __repr__(self):
269 def __repr__(self):
250 return ustr
270 return ustr
251
271
252 c = C()
272 c = C()
253 p = pretty.pretty(c)
273 p = pretty.pretty(c)
254 nt.assert_equal(p, u)
274 nt.assert_equal(p, u)
255 p = pretty.pretty([c])
275 p = pretty.pretty([c])
256 nt.assert_equal(p, u'[%s]' % u)
276 nt.assert_equal(p, u'[%s]' % u)
257
277
258
278
259 def test_basic_class():
279 def test_basic_class():
260 def type_pprint_wrapper(obj, p, cycle):
280 def type_pprint_wrapper(obj, p, cycle):
261 if obj is MyObj:
281 if obj is MyObj:
262 type_pprint_wrapper.called = True
282 type_pprint_wrapper.called = True
263 return pretty._type_pprint(obj, p, cycle)
283 return pretty._type_pprint(obj, p, cycle)
264 type_pprint_wrapper.called = False
284 type_pprint_wrapper.called = False
265
285
266 stream = StringIO()
286 stream = StringIO()
267 printer = pretty.RepresentationPrinter(stream)
287 printer = pretty.RepresentationPrinter(stream)
268 printer.type_pprinters[type] = type_pprint_wrapper
288 printer.type_pprinters[type] = type_pprint_wrapper
269 printer.pretty(MyObj)
289 printer.pretty(MyObj)
270 printer.flush()
290 printer.flush()
271 output = stream.getvalue()
291 output = stream.getvalue()
272
292
273 nt.assert_equal(output, '%s.MyObj' % __name__)
293 nt.assert_equal(output, '%s.MyObj' % __name__)
274 nt.assert_true(type_pprint_wrapper.called)
294 nt.assert_true(type_pprint_wrapper.called)
275
295
276
296
277 def test_collections_defaultdict():
297 def test_collections_defaultdict():
278 # Create defaultdicts with cycles
298 # Create defaultdicts with cycles
279 a = defaultdict()
299 a = defaultdict()
280 a.default_factory = a
300 a.default_factory = a
281 b = defaultdict(list)
301 b = defaultdict(list)
282 b['key'] = b
302 b['key'] = b
283
303
284 # Dictionary order cannot be relied on, test against single keys.
304 # Dictionary order cannot be relied on, test against single keys.
285 cases = [
305 cases = [
286 (defaultdict(list), 'defaultdict(list, {})'),
306 (defaultdict(list), 'defaultdict(list, {})'),
287 (defaultdict(list, {'key': '-' * 50}),
307 (defaultdict(list, {'key': '-' * 50}),
288 "defaultdict(list,\n"
308 "defaultdict(list,\n"
289 " {'key': '--------------------------------------------------'})"),
309 " {'key': '--------------------------------------------------'})"),
290 (a, 'defaultdict(defaultdict(...), {})'),
310 (a, 'defaultdict(defaultdict(...), {})'),
291 (b, "defaultdict(list, {'key': defaultdict(...)})"),
311 (b, "defaultdict(list, {'key': defaultdict(...)})"),
292 ]
312 ]
293 for obj, expected in cases:
313 for obj, expected in cases:
294 nt.assert_equal(pretty.pretty(obj), expected)
314 nt.assert_equal(pretty.pretty(obj), expected)
295
315
296
316
297 def test_collections_ordereddict():
317 def test_collections_ordereddict():
298 # Create OrderedDict with cycle
318 # Create OrderedDict with cycle
299 a = OrderedDict()
319 a = OrderedDict()
300 a['key'] = a
320 a['key'] = a
301
321
302 cases = [
322 cases = [
303 (OrderedDict(), 'OrderedDict()'),
323 (OrderedDict(), 'OrderedDict()'),
304 (OrderedDict((i, i) for i in range(1000, 1010)),
324 (OrderedDict((i, i) for i in range(1000, 1010)),
305 'OrderedDict([(1000, 1000),\n'
325 'OrderedDict([(1000, 1000),\n'
306 ' (1001, 1001),\n'
326 ' (1001, 1001),\n'
307 ' (1002, 1002),\n'
327 ' (1002, 1002),\n'
308 ' (1003, 1003),\n'
328 ' (1003, 1003),\n'
309 ' (1004, 1004),\n'
329 ' (1004, 1004),\n'
310 ' (1005, 1005),\n'
330 ' (1005, 1005),\n'
311 ' (1006, 1006),\n'
331 ' (1006, 1006),\n'
312 ' (1007, 1007),\n'
332 ' (1007, 1007),\n'
313 ' (1008, 1008),\n'
333 ' (1008, 1008),\n'
314 ' (1009, 1009)])'),
334 ' (1009, 1009)])'),
315 (a, "OrderedDict([('key', OrderedDict(...))])"),
335 (a, "OrderedDict([('key', OrderedDict(...))])"),
316 ]
336 ]
317 for obj, expected in cases:
337 for obj, expected in cases:
318 nt.assert_equal(pretty.pretty(obj), expected)
338 nt.assert_equal(pretty.pretty(obj), expected)
319
339
320
340
321 def test_collections_deque():
341 def test_collections_deque():
322 # Create deque with cycle
342 # Create deque with cycle
323 a = deque()
343 a = deque()
324 a.append(a)
344 a.append(a)
325
345
326 cases = [
346 cases = [
327 (deque(), 'deque([])'),
347 (deque(), 'deque([])'),
328 (deque(i for i in range(1000, 1020)),
348 (deque(i for i in range(1000, 1020)),
329 'deque([1000,\n'
349 'deque([1000,\n'
330 ' 1001,\n'
350 ' 1001,\n'
331 ' 1002,\n'
351 ' 1002,\n'
332 ' 1003,\n'
352 ' 1003,\n'
333 ' 1004,\n'
353 ' 1004,\n'
334 ' 1005,\n'
354 ' 1005,\n'
335 ' 1006,\n'
355 ' 1006,\n'
336 ' 1007,\n'
356 ' 1007,\n'
337 ' 1008,\n'
357 ' 1008,\n'
338 ' 1009,\n'
358 ' 1009,\n'
339 ' 1010,\n'
359 ' 1010,\n'
340 ' 1011,\n'
360 ' 1011,\n'
341 ' 1012,\n'
361 ' 1012,\n'
342 ' 1013,\n'
362 ' 1013,\n'
343 ' 1014,\n'
363 ' 1014,\n'
344 ' 1015,\n'
364 ' 1015,\n'
345 ' 1016,\n'
365 ' 1016,\n'
346 ' 1017,\n'
366 ' 1017,\n'
347 ' 1018,\n'
367 ' 1018,\n'
348 ' 1019])'),
368 ' 1019])'),
349 (a, 'deque([deque(...)])'),
369 (a, 'deque([deque(...)])'),
350 ]
370 ]
351 for obj, expected in cases:
371 for obj, expected in cases:
352 nt.assert_equal(pretty.pretty(obj), expected)
372 nt.assert_equal(pretty.pretty(obj), expected)
353
373
354 def test_collections_counter():
374 def test_collections_counter():
355 class MyCounter(Counter):
375 class MyCounter(Counter):
356 pass
376 pass
357 cases = [
377 cases = [
358 (Counter(), 'Counter()'),
378 (Counter(), 'Counter()'),
359 (Counter(a=1), "Counter({'a': 1})"),
379 (Counter(a=1), "Counter({'a': 1})"),
360 (MyCounter(a=1), "MyCounter({'a': 1})"),
380 (MyCounter(a=1), "MyCounter({'a': 1})"),
361 ]
381 ]
362 for obj, expected in cases:
382 for obj, expected in cases:
363 nt.assert_equal(pretty.pretty(obj), expected)
383 nt.assert_equal(pretty.pretty(obj), expected)
364
384
365 def test_mappingproxy():
385 def test_mappingproxy():
366 MP = types.MappingProxyType
386 MP = types.MappingProxyType
367 underlying_dict = {}
387 underlying_dict = {}
368 mp_recursive = MP(underlying_dict)
388 mp_recursive = MP(underlying_dict)
369 underlying_dict[2] = mp_recursive
389 underlying_dict[2] = mp_recursive
370 underlying_dict[3] = underlying_dict
390 underlying_dict[3] = underlying_dict
371
391
372 cases = [
392 cases = [
373 (MP({}), "mappingproxy({})"),
393 (MP({}), "mappingproxy({})"),
374 (MP({None: MP({})}), "mappingproxy({None: mappingproxy({})})"),
394 (MP({None: MP({})}), "mappingproxy({None: mappingproxy({})})"),
375 (MP({k: k.upper() for k in string.ascii_lowercase}),
395 (MP({k: k.upper() for k in string.ascii_lowercase}),
376 "mappingproxy({'a': 'A',\n"
396 "mappingproxy({'a': 'A',\n"
377 " 'b': 'B',\n"
397 " 'b': 'B',\n"
378 " 'c': 'C',\n"
398 " 'c': 'C',\n"
379 " 'd': 'D',\n"
399 " 'd': 'D',\n"
380 " 'e': 'E',\n"
400 " 'e': 'E',\n"
381 " 'f': 'F',\n"
401 " 'f': 'F',\n"
382 " 'g': 'G',\n"
402 " 'g': 'G',\n"
383 " 'h': 'H',\n"
403 " 'h': 'H',\n"
384 " 'i': 'I',\n"
404 " 'i': 'I',\n"
385 " 'j': 'J',\n"
405 " 'j': 'J',\n"
386 " 'k': 'K',\n"
406 " 'k': 'K',\n"
387 " 'l': 'L',\n"
407 " 'l': 'L',\n"
388 " 'm': 'M',\n"
408 " 'm': 'M',\n"
389 " 'n': 'N',\n"
409 " 'n': 'N',\n"
390 " 'o': 'O',\n"
410 " 'o': 'O',\n"
391 " 'p': 'P',\n"
411 " 'p': 'P',\n"
392 " 'q': 'Q',\n"
412 " 'q': 'Q',\n"
393 " 'r': 'R',\n"
413 " 'r': 'R',\n"
394 " 's': 'S',\n"
414 " 's': 'S',\n"
395 " 't': 'T',\n"
415 " 't': 'T',\n"
396 " 'u': 'U',\n"
416 " 'u': 'U',\n"
397 " 'v': 'V',\n"
417 " 'v': 'V',\n"
398 " 'w': 'W',\n"
418 " 'w': 'W',\n"
399 " 'x': 'X',\n"
419 " 'x': 'X',\n"
400 " 'y': 'Y',\n"
420 " 'y': 'Y',\n"
401 " 'z': 'Z'})"),
421 " 'z': 'Z'})"),
402 (mp_recursive, "mappingproxy({2: {...}, 3: {2: {...}, 3: {...}}})"),
422 (mp_recursive, "mappingproxy({2: {...}, 3: {2: {...}, 3: {...}}})"),
403 (underlying_dict,
423 (underlying_dict,
404 "{2: mappingproxy({2: {...}, 3: {...}}), 3: {...}}"),
424 "{2: mappingproxy({2: {...}, 3: {...}}), 3: {...}}"),
405 ]
425 ]
406 for obj, expected in cases:
426 for obj, expected in cases:
407 nt.assert_equal(pretty.pretty(obj), expected)
427 nt.assert_equal(pretty.pretty(obj), expected)
408
428
409
429
410 def test_simplenamespace():
430 def test_simplenamespace():
411 SN = types.SimpleNamespace
431 SN = types.SimpleNamespace
412
432
413 sn_recursive = SN()
433 sn_recursive = SN()
414 sn_recursive.first = sn_recursive
434 sn_recursive.first = sn_recursive
415 sn_recursive.second = sn_recursive
435 sn_recursive.second = sn_recursive
416 cases = [
436 cases = [
417 (SN(), "namespace()"),
437 (SN(), "namespace()"),
418 (SN(x=SN()), "namespace(x=namespace())"),
438 (SN(x=SN()), "namespace(x=namespace())"),
419 (SN(a_long_name=[SN(s=string.ascii_lowercase)]*3, a_short_name=None),
439 (SN(a_long_name=[SN(s=string.ascii_lowercase)]*3, a_short_name=None),
420 "namespace(a_long_name=[namespace(s='abcdefghijklmnopqrstuvwxyz'),\n"
440 "namespace(a_long_name=[namespace(s='abcdefghijklmnopqrstuvwxyz'),\n"
421 " namespace(s='abcdefghijklmnopqrstuvwxyz'),\n"
441 " namespace(s='abcdefghijklmnopqrstuvwxyz'),\n"
422 " namespace(s='abcdefghijklmnopqrstuvwxyz')],\n"
442 " namespace(s='abcdefghijklmnopqrstuvwxyz')],\n"
423 " a_short_name=None)"),
443 " a_short_name=None)"),
424 (sn_recursive, "namespace(first=namespace(...), second=namespace(...))"),
444 (sn_recursive, "namespace(first=namespace(...), second=namespace(...))"),
425 ]
445 ]
426 for obj, expected in cases:
446 for obj, expected in cases:
427 nt.assert_equal(pretty.pretty(obj), expected)
447 nt.assert_equal(pretty.pretty(obj), expected)
428
448
429
449
430 def test_pretty_environ():
450 def test_pretty_environ():
431 dict_repr = pretty.pretty(dict(os.environ))
451 dict_repr = pretty.pretty(dict(os.environ))
432 # reindent to align with 'environ' prefix
452 # reindent to align with 'environ' prefix
433 dict_indented = dict_repr.replace('\n', '\n' + (' ' * len('environ')))
453 dict_indented = dict_repr.replace('\n', '\n' + (' ' * len('environ')))
434 env_repr = pretty.pretty(os.environ)
454 env_repr = pretty.pretty(os.environ)
435 nt.assert_equal(env_repr, 'environ' + dict_indented)
455 nt.assert_equal(env_repr, 'environ' + dict_indented)
436
456
437
457
438 def test_function_pretty():
458 def test_function_pretty():
439 "Test pretty print of function"
459 "Test pretty print of function"
440 # posixpath is a pure python module, its interface is consistent
460 # posixpath is a pure python module, its interface is consistent
441 # across Python distributions
461 # across Python distributions
442 import posixpath
462 import posixpath
443 nt.assert_equal(pretty.pretty(posixpath.join), '<function posixpath.join(a, *p)>')
463 nt.assert_equal(pretty.pretty(posixpath.join), '<function posixpath.join(a, *p)>')
444
464
445 # custom function
465 # custom function
446 def meaning_of_life(question=None):
466 def meaning_of_life(question=None):
447 if question:
467 if question:
448 return 42
468 return 42
449 return "Don't panic"
469 return "Don't panic"
450
470
451 nt.assert_in('meaning_of_life(question=None)', pretty.pretty(meaning_of_life))
471 nt.assert_in('meaning_of_life(question=None)', pretty.pretty(meaning_of_life))
452
472
453
473
454 class OrderedCounter(Counter, OrderedDict):
474 class OrderedCounter(Counter, OrderedDict):
455 'Counter that remembers the order elements are first encountered'
475 'Counter that remembers the order elements are first encountered'
456
476
457 def __repr__(self):
477 def __repr__(self):
458 return '%s(%r)' % (self.__class__.__name__, OrderedDict(self))
478 return '%s(%r)' % (self.__class__.__name__, OrderedDict(self))
459
479
460 def __reduce__(self):
480 def __reduce__(self):
461 return self.__class__, (OrderedDict(self),)
481 return self.__class__, (OrderedDict(self),)
462
482
463 class MySet(set): # Override repr of a basic type
483 class MySet(set): # Override repr of a basic type
464 def __repr__(self):
484 def __repr__(self):
465 return 'mine'
485 return 'mine'
466
486
467 def test_custom_repr():
487 def test_custom_repr():
468 """A custom repr should override a pretty printer for a parent type"""
488 """A custom repr should override a pretty printer for a parent type"""
469 oc = OrderedCounter("abracadabra")
489 oc = OrderedCounter("abracadabra")
470 nt.assert_in("OrderedCounter(OrderedDict", pretty.pretty(oc))
490 nt.assert_in("OrderedCounter(OrderedDict", pretty.pretty(oc))
471
491
472 nt.assert_equal(pretty.pretty(MySet()), 'mine')
492 nt.assert_equal(pretty.pretty(MySet()), 'mine')
@@ -1,383 +1,399 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
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
48 # occasionally update from upstream: decorators.py is a copy of
49 # numpy.testing.decorators, we expose all of it here.
49 # numpy.testing.decorators, we expose all of it here.
50 from IPython.external.decorators import knownfailureif
50 from IPython.external.decorators import knownfailureif
51
51
52 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
53 # Classes and functions
53 # Classes and functions
54 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
55
55
56 # Simple example of the basic idea
56 # Simple example of the basic idea
57 def as_unittest(func):
57 def as_unittest(func):
58 """Decorator to make a simple function into a normal test via unittest."""
58 """Decorator to make a simple function into a normal test via unittest."""
59 class Tester(unittest.TestCase):
59 class Tester(unittest.TestCase):
60 def test(self):
60 def test(self):
61 func()
61 func()
62
62
63 Tester.__name__ = func.__name__
63 Tester.__name__ = func.__name__
64
64
65 return Tester
65 return Tester
66
66
67 # Utility functions
67 # Utility functions
68
68
69 def apply_wrapper(wrapper, func):
69 def apply_wrapper(wrapper, func):
70 """Apply a wrapper to a function for decoration.
70 """Apply a wrapper to a function for decoration.
71
71
72 This mixes Michele Simionato's decorator tool with nose's make_decorator,
72 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
73 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.
74 function signature and other properties, survive the decoration cleanly.
75 This will ensure that wrapped functions can still be well introspected via
75 This will ensure that wrapped functions can still be well introspected via
76 IPython, for example.
76 IPython, for example.
77 """
77 """
78 warnings.warn("The function `apply_wrapper` is deprecated since IPython 4.0",
78 warnings.warn("The function `apply_wrapper` is deprecated since IPython 4.0",
79 DeprecationWarning, stacklevel=2)
79 DeprecationWarning, stacklevel=2)
80 import nose.tools
80 import nose.tools
81
81
82 return decorator(wrapper,nose.tools.make_decorator(func)(wrapper))
82 return decorator(wrapper,nose.tools.make_decorator(func)(wrapper))
83
83
84
84
85 def make_label_dec(label, ds=None):
85 def make_label_dec(label, ds=None):
86 """Factory function to create a decorator that applies one or more labels.
86 """Factory function to create a decorator that applies one or more labels.
87
87
88 Parameters
88 Parameters
89 ----------
89 ----------
90 label : string or sequence
90 label : string or sequence
91 One or more labels that will be applied by the decorator to the functions
91 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
92 it decorates. Labels are attributes of the decorated function with their
93 value set to True.
93 value set to True.
94
94
95 ds : string
95 ds : string
96 An optional docstring for the resulting decorator. If not given, a
96 An optional docstring for the resulting decorator. If not given, a
97 default docstring is auto-generated.
97 default docstring is auto-generated.
98
98
99 Returns
99 Returns
100 -------
100 -------
101 A decorator.
101 A decorator.
102
102
103 Examples
103 Examples
104 --------
104 --------
105
105
106 A simple labeling decorator:
106 A simple labeling decorator:
107
107
108 >>> slow = make_label_dec('slow')
108 >>> slow = make_label_dec('slow')
109 >>> slow.__doc__
109 >>> slow.__doc__
110 "Labels a test as 'slow'."
110 "Labels a test as 'slow'."
111
111
112 And one that uses multiple labels and a custom docstring:
112 And one that uses multiple labels and a custom docstring:
113
113
114 >>> rare = make_label_dec(['slow','hard'],
114 >>> rare = make_label_dec(['slow','hard'],
115 ... "Mix labels 'slow' and 'hard' for rare tests.")
115 ... "Mix labels 'slow' and 'hard' for rare tests.")
116 >>> rare.__doc__
116 >>> rare.__doc__
117 "Mix labels 'slow' and 'hard' for rare tests."
117 "Mix labels 'slow' and 'hard' for rare tests."
118
118
119 Now, let's test using this one:
119 Now, let's test using this one:
120 >>> @rare
120 >>> @rare
121 ... def f(): pass
121 ... def f(): pass
122 ...
122 ...
123 >>>
123 >>>
124 >>> f.slow
124 >>> f.slow
125 True
125 True
126 >>> f.hard
126 >>> f.hard
127 True
127 True
128 """
128 """
129
129
130 warnings.warn("The function `make_label_dec` is deprecated since IPython 4.0",
130 warnings.warn("The function `make_label_dec` is deprecated since IPython 4.0",
131 DeprecationWarning, stacklevel=2)
131 DeprecationWarning, stacklevel=2)
132 if isinstance(label, str):
132 if isinstance(label, str):
133 labels = [label]
133 labels = [label]
134 else:
134 else:
135 labels = label
135 labels = label
136
136
137 # Validate that the given label(s) are OK for use in setattr() by doing a
137 # Validate that the given label(s) are OK for use in setattr() by doing a
138 # dry run on a dummy function.
138 # dry run on a dummy function.
139 tmp = lambda : None
139 tmp = lambda : None
140 for label in labels:
140 for label in labels:
141 setattr(tmp,label,True)
141 setattr(tmp,label,True)
142
142
143 # This is the actual decorator we'll return
143 # This is the actual decorator we'll return
144 def decor(f):
144 def decor(f):
145 for label in labels:
145 for label in labels:
146 setattr(f,label,True)
146 setattr(f,label,True)
147 return f
147 return f
148
148
149 # Apply the user's docstring, or autogenerate a basic one
149 # Apply the user's docstring, or autogenerate a basic one
150 if ds is None:
150 if ds is None:
151 ds = "Labels a test as %r." % label
151 ds = "Labels a test as %r." % label
152 decor.__doc__ = ds
152 decor.__doc__ = ds
153
153
154 return decor
154 return decor
155
155
156
156
157 def skip_iptest_but_not_pytest(f):
158 """
159 Warnign this will make the test invisible to iptest.
160 """
161 import os
162
163 if os.environ.get("IPTEST_WORKING_DIR", None) is not None:
164 f.__test__ = False
165 return f
166
167
157 # Inspired by numpy's skipif, but uses the full apply_wrapper utility to
168 # Inspired by numpy's skipif, but uses the full apply_wrapper utility to
158 # preserve function metadata better and allows the skip condition to be a
169 # preserve function metadata better and allows the skip condition to be a
159 # callable.
170 # callable.
160 def skipif(skip_condition, msg=None):
171 def skipif(skip_condition, msg=None):
161 ''' Make function raise SkipTest exception if skip_condition is true
172 ''' Make function raise SkipTest exception if skip_condition is true
162
173
163 Parameters
174 Parameters
164 ----------
175 ----------
165
176
166 skip_condition : bool or callable
177 skip_condition : bool or callable
167 Flag to determine whether to skip test. If the condition is a
178 Flag to determine whether to skip test. If the condition is a
168 callable, it is used at runtime to dynamically make the decision. This
179 callable, it is used at runtime to dynamically make the decision. This
169 is useful for tests that may require costly imports, to delay the cost
180 is useful for tests that may require costly imports, to delay the cost
170 until the test suite is actually executed.
181 until the test suite is actually executed.
171 msg : string
182 msg : string
172 Message to give on raising a SkipTest exception.
183 Message to give on raising a SkipTest exception.
173
184
174 Returns
185 Returns
175 -------
186 -------
176 decorator : function
187 decorator : function
177 Decorator, which, when applied to a function, causes SkipTest
188 Decorator, which, when applied to a function, causes SkipTest
178 to be raised when the skip_condition was True, and the function
189 to be raised when the skip_condition was True, and the function
179 to be called normally otherwise.
190 to be called normally otherwise.
180
191
181 Notes
192 Notes
182 -----
193 -----
183 You will see from the code that we had to further decorate the
194 You will see from the code that we had to further decorate the
184 decorator with the nose.tools.make_decorator function in order to
195 decorator with the nose.tools.make_decorator function in order to
185 transmit function name, and various other metadata.
196 transmit function name, and various other metadata.
186 '''
197 '''
187
198
188 def skip_decorator(f):
199 def skip_decorator(f):
189 # Local import to avoid a hard nose dependency and only incur the
200 # Local import to avoid a hard nose dependency and only incur the
190 # import time overhead at actual test-time.
201 # import time overhead at actual test-time.
191 import nose
202 import nose
192
203
193 # Allow for both boolean or callable skip conditions.
204 # Allow for both boolean or callable skip conditions.
194 if callable(skip_condition):
205 if callable(skip_condition):
195 skip_val = skip_condition
206 skip_val = skip_condition
196 else:
207 else:
197 skip_val = lambda : skip_condition
208 skip_val = lambda : skip_condition
198
209
199 def get_msg(func,msg=None):
210 def get_msg(func,msg=None):
200 """Skip message with information about function being skipped."""
211 """Skip message with information about function being skipped."""
201 if msg is None: out = 'Test skipped due to test condition.'
212 if msg is None: out = 'Test skipped due to test condition.'
202 else: out = msg
213 else: out = msg
203 return "Skipping test: %s. %s" % (func.__name__,out)
214 return "Skipping test: %s. %s" % (func.__name__,out)
204
215
205 # We need to define *two* skippers because Python doesn't allow both
216 # We need to define *two* skippers because Python doesn't allow both
206 # return with value and yield inside the same function.
217 # return with value and yield inside the same function.
207 def skipper_func(*args, **kwargs):
218 def skipper_func(*args, **kwargs):
208 """Skipper for normal test functions."""
219 """Skipper for normal test functions."""
209 if skip_val():
220 if skip_val():
210 raise nose.SkipTest(get_msg(f,msg))
221 raise nose.SkipTest(get_msg(f,msg))
211 else:
222 else:
212 return f(*args, **kwargs)
223 return f(*args, **kwargs)
213
224
214 def skipper_gen(*args, **kwargs):
225 def skipper_gen(*args, **kwargs):
215 """Skipper for test generators."""
226 """Skipper for test generators."""
216 if skip_val():
227 if skip_val():
217 raise nose.SkipTest(get_msg(f,msg))
228 raise nose.SkipTest(get_msg(f,msg))
218 else:
229 else:
219 for x in f(*args, **kwargs):
230 for x in f(*args, **kwargs):
220 yield x
231 yield x
221
232
222 # Choose the right skipper to use when building the actual generator.
233 # Choose the right skipper to use when building the actual generator.
223 if nose.util.isgenerator(f):
234 if nose.util.isgenerator(f):
224 skipper = skipper_gen
235 skipper = skipper_gen
225 else:
236 else:
226 skipper = skipper_func
237 skipper = skipper_func
227
238
228 return nose.tools.make_decorator(f)(skipper)
239 return nose.tools.make_decorator(f)(skipper)
229
240
230 return skip_decorator
241 return skip_decorator
231
242
232 # A version with the condition set to true, common case just to attach a message
243 # A version with the condition set to true, common case just to attach a message
233 # to a skip decorator
244 # to a skip decorator
234 def skip(msg=None):
245 def skip(msg=None):
235 """Decorator factory - mark a test function for skipping from test suite.
246 """Decorator factory - mark a test function for skipping from test suite.
236
247
237 Parameters
248 Parameters
238 ----------
249 ----------
239 msg : string
250 msg : string
240 Optional message to be added.
251 Optional message to be added.
241
252
242 Returns
253 Returns
243 -------
254 -------
244 decorator : function
255 decorator : function
245 Decorator, which, when applied to a function, causes SkipTest
256 Decorator, which, when applied to a function, causes SkipTest
246 to be raised, with the optional message added.
257 to be raised, with the optional message added.
247 """
258 """
248 if msg and not isinstance(msg, str):
259 if msg and not isinstance(msg, str):
249 raise ValueError('invalid object passed to `@skip` decorator, did you '
260 raise ValueError('invalid object passed to `@skip` decorator, did you '
250 'meant `@skip()` with brackets ?')
261 'meant `@skip()` with brackets ?')
251 return skipif(True, msg)
262 return skipif(True, msg)
252
263
253
264
254 def onlyif(condition, msg):
265 def onlyif(condition, msg):
255 """The reverse from skipif, see skipif for details."""
266 """The reverse from skipif, see skipif for details."""
256
267
257 if callable(condition):
268 if callable(condition):
258 skip_condition = lambda : not condition()
269 skip_condition = lambda : not condition()
259 else:
270 else:
260 skip_condition = lambda : not condition
271 skip_condition = lambda : not condition
261
272
262 return skipif(skip_condition, msg)
273 return skipif(skip_condition, msg)
263
274
264 #-----------------------------------------------------------------------------
275 #-----------------------------------------------------------------------------
265 # Utility functions for decorators
276 # Utility functions for decorators
266 def module_not_available(module):
277 def module_not_available(module):
267 """Can module be imported? Returns true if module does NOT import.
278 """Can module be imported? Returns true if module does NOT import.
268
279
269 This is used to make a decorator to skip tests that require module to be
280 This is used to make a decorator to skip tests that require module to be
270 available, but delay the 'import numpy' to test execution time.
281 available, but delay the 'import numpy' to test execution time.
271 """
282 """
272 try:
283 try:
273 mod = import_module(module)
284 mod = import_module(module)
274 mod_not_avail = False
285 mod_not_avail = False
275 except ImportError:
286 except ImportError:
276 mod_not_avail = True
287 mod_not_avail = True
277
288
278 return mod_not_avail
289 return mod_not_avail
279
290
280
291
281 def decorated_dummy(dec, name):
292 def decorated_dummy(dec, name):
282 """Return a dummy function decorated with dec, with the given name.
293 """Return a dummy function decorated with dec, with the given name.
283
294
284 Examples
295 Examples
285 --------
296 --------
286 import IPython.testing.decorators as dec
297 import IPython.testing.decorators as dec
287 setup = dec.decorated_dummy(dec.skip_if_no_x11, __name__)
298 setup = dec.decorated_dummy(dec.skip_if_no_x11, __name__)
288 """
299 """
289 warnings.warn("The function `decorated_dummy` is deprecated since IPython 4.0",
300 warnings.warn("The function `decorated_dummy` is deprecated since IPython 4.0",
290 DeprecationWarning, stacklevel=2)
301 DeprecationWarning, stacklevel=2)
291 dummy = lambda: None
302 dummy = lambda: None
292 dummy.__name__ = name
303 dummy.__name__ = name
293 return dec(dummy)
304 return dec(dummy)
294
305
295 #-----------------------------------------------------------------------------
306 #-----------------------------------------------------------------------------
296 # Decorators for public use
307 # Decorators for public use
297
308
298 # Decorators to skip certain tests on specific platforms.
309 # Decorators to skip certain tests on specific platforms.
299 skip_win32 = skipif(sys.platform == 'win32',
310 skip_win32 = skipif(sys.platform == 'win32',
300 "This test does not run under Windows")
311 "This test does not run under Windows")
301 skip_linux = skipif(sys.platform.startswith('linux'),
312 skip_linux = skipif(sys.platform.startswith('linux'),
302 "This test does not run under Linux")
313 "This test does not run under Linux")
303 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
314 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
304
315
305
316
306 # Decorators to skip tests if not on specific platforms.
317 # Decorators to skip tests if not on specific platforms.
307 skip_if_not_win32 = skipif(sys.platform != 'win32',
318 skip_if_not_win32 = skipif(sys.platform != 'win32',
308 "This test only runs under Windows")
319 "This test only runs under Windows")
309 skip_if_not_linux = skipif(not sys.platform.startswith('linux'),
320 skip_if_not_linux = skipif(not sys.platform.startswith('linux'),
310 "This test only runs under Linux")
321 "This test only runs under Linux")
311 skip_if_not_osx = skipif(sys.platform != 'darwin',
322 skip_if_not_osx = skipif(sys.platform != 'darwin',
312 "This test only runs under OSX")
323 "This test only runs under OSX")
313
324
314
325
315 _x11_skip_cond = (sys.platform not in ('darwin', 'win32') and
326 _x11_skip_cond = (sys.platform not in ('darwin', 'win32') and
316 os.environ.get('DISPLAY', '') == '')
327 os.environ.get('DISPLAY', '') == '')
317 _x11_skip_msg = "Skipped under *nix when X11/XOrg not available"
328 _x11_skip_msg = "Skipped under *nix when X11/XOrg not available"
318
329
319 skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg)
330 skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg)
320
331
321
332
322 # Decorators to skip certain tests on specific platform/python combinations
333 # Decorators to skip certain tests on specific platform/python combinations
323 skip_win32_py38 = skipif(sys.version_info > (3,8) and os.name == 'nt')
334 skip_win32_py38 = skipif(sys.version_info > (3,8) and os.name == 'nt')
324
335
325
336
326 # not a decorator itself, returns a dummy function to be used as setup
337 # not a decorator itself, returns a dummy function to be used as setup
327 def skip_file_no_x11(name):
338 def skip_file_no_x11(name):
328 warnings.warn("The function `skip_file_no_x11` is deprecated since IPython 4.0",
339 warnings.warn("The function `skip_file_no_x11` is deprecated since IPython 4.0",
329 DeprecationWarning, stacklevel=2)
340 DeprecationWarning, stacklevel=2)
330 return decorated_dummy(skip_if_no_x11, name) if _x11_skip_cond else None
341 return decorated_dummy(skip_if_no_x11, name) if _x11_skip_cond else None
331
342
332 # Other skip decorators
343 # Other skip decorators
333
344
334 # generic skip without module
345 # generic skip without module
335 skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
346 skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
336
347
337 skipif_not_numpy = skip_without('numpy')
348 skipif_not_numpy = skip_without('numpy')
338
349
339 skipif_not_matplotlib = skip_without('matplotlib')
350 skipif_not_matplotlib = skip_without('matplotlib')
340
351
341 skipif_not_sympy = skip_without('sympy')
352 skipif_not_sympy = skip_without('sympy')
342
353
343 skip_known_failure = knownfailureif(True,'This test is known to fail')
354 skip_known_failure = knownfailureif(True,'This test is known to fail')
344
355
345 # A null 'decorator', useful to make more readable code that needs to pick
356 # A null 'decorator', useful to make more readable code that needs to pick
346 # between different decorators based on OS or other conditions
357 # between different decorators based on OS or other conditions
347 null_deco = lambda f: f
358 null_deco = lambda f: f
348
359
349 # Some tests only run where we can use unicode paths. Note that we can't just
360 # Some tests only run where we can use unicode paths. Note that we can't just
350 # check os.path.supports_unicode_filenames, which is always False on Linux.
361 # check os.path.supports_unicode_filenames, which is always False on Linux.
351 try:
362 try:
352 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
363 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
353 except UnicodeEncodeError:
364 except UnicodeEncodeError:
354 unicode_paths = False
365 unicode_paths = False
355 else:
366 else:
356 unicode_paths = True
367 unicode_paths = True
357 f.close()
368 f.close()
358
369
359 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
370 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
360 "where we can use unicode in filenames."))
371 "where we can use unicode in filenames."))
361
372
362
373
363 def onlyif_cmds_exist(*commands):
374 def onlyif_cmds_exist(*commands):
364 """
375 """
365 Decorator to skip test when at least one of `commands` is not found.
376 Decorator to skip test when at least one of `commands` is not found.
366 """
377 """
367 for cmd in commands:
378 for cmd in commands:
379 reason = "This test runs only if command '{cmd}' is installed"
368 if not shutil.which(cmd):
380 if not shutil.which(cmd):
369 return skip("This test runs only if command '{0}' "
381 if os.environ.get("IPTEST_WORKING_DIR", None) is not None:
370 "is installed".format(cmd))
382 return skip(reason)
383 else:
384 import pytest
385
386 return pytest.mark.skip(reason=reason)
371 return null_deco
387 return null_deco
372
388
373 def onlyif_any_cmd_exists(*commands):
389 def onlyif_any_cmd_exists(*commands):
374 """
390 """
375 Decorator to skip test unless at least one of `commands` is found.
391 Decorator to skip test unless at least one of `commands` is found.
376 """
392 """
377 warnings.warn("The function `onlyif_any_cmd_exists` is deprecated since IPython 4.0",
393 warnings.warn("The function `onlyif_any_cmd_exists` is deprecated since IPython 4.0",
378 DeprecationWarning, stacklevel=2)
394 DeprecationWarning, stacklevel=2)
379 for cmd in commands:
395 for cmd in commands:
380 if shutil.which(cmd):
396 if shutil.which(cmd):
381 return null_deco
397 return null_deco
382 return skip("This test runs only if one of the commands {0} "
398 return skip("This test runs only if one of the commands {0} "
383 "is installed".format(commands))
399 "is installed".format(commands))
@@ -1,159 +1,172 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for IPython.utils.capture"""
2 """Tests for IPython.utils.capture"""
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2013 The IPython Development Team
5 # Copyright (C) 2013 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 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15
15
16 import sys
16 import sys
17
17
18 import nose.tools as nt
18 import nose.tools as nt
19 import pytest
20
21 from IPython.testing.decorators import skip_iptest_but_not_pytest
19
22
20 from IPython.utils import capture
23 from IPython.utils import capture
21
24
22 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
23 # Globals
26 # Globals
24 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
25
28
26 _mime_map = dict(
29 _mime_map = dict(
27 _repr_png_="image/png",
30 _repr_png_="image/png",
28 _repr_jpeg_="image/jpeg",
31 _repr_jpeg_="image/jpeg",
29 _repr_svg_="image/svg+xml",
32 _repr_svg_="image/svg+xml",
30 _repr_html_="text/html",
33 _repr_html_="text/html",
31 _repr_json_="application/json",
34 _repr_json_="application/json",
32 _repr_javascript_="application/javascript",
35 _repr_javascript_="application/javascript",
33 )
36 )
34
37
35 basic_data = {
38 basic_data = {
36 'image/png' : b'binarydata',
39 'image/png' : b'binarydata',
37 'text/html' : "<b>bold</b>",
40 'text/html' : "<b>bold</b>",
38 }
41 }
39 basic_metadata = {
42 basic_metadata = {
40 'image/png' : {
43 'image/png' : {
41 'width' : 10,
44 'width' : 10,
42 'height' : 20,
45 'height' : 20,
43 },
46 },
44 }
47 }
45
48
46 full_data = {
49 full_data = {
47 'image/png' : b'binarydata',
50 'image/png' : b'binarydata',
48 'image/jpeg' : b'binarydata',
51 'image/jpeg' : b'binarydata',
49 'image/svg+xml' : "<svg>",
52 'image/svg+xml' : "<svg>",
50 'text/html' : "<b>bold</b>",
53 'text/html' : "<b>bold</b>",
51 'application/javascript' : "alert();",
54 'application/javascript' : "alert();",
52 'application/json' : "{}",
55 'application/json' : "{}",
53 }
56 }
54 full_metadata = {
57 full_metadata = {
55 'image/png' : {"png" : "exists"},
58 'image/png' : {"png" : "exists"},
56 'image/jpeg' : {"jpeg" : "exists"},
59 'image/jpeg' : {"jpeg" : "exists"},
57 'image/svg+xml' : {"svg" : "exists"},
60 'image/svg+xml' : {"svg" : "exists"},
58 'text/html' : {"html" : "exists"},
61 'text/html' : {"html" : "exists"},
59 'application/javascript' : {"js" : "exists"},
62 'application/javascript' : {"js" : "exists"},
60 'application/json' : {"json" : "exists"},
63 'application/json' : {"json" : "exists"},
61 }
64 }
62
65
63 hello_stdout = "hello, stdout"
66 hello_stdout = "hello, stdout"
64 hello_stderr = "hello, stderr"
67 hello_stderr = "hello, stderr"
65
68
66 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
67 # Test Functions
70 # Test Functions
68 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
69
72 @pytest.mark.parametrize("method_mime", _mime_map.items())
70 def test_rich_output_empty():
73 @skip_iptest_but_not_pytest
74 def test_rich_output_empty(method_mime):
71 """RichOutput with no args"""
75 """RichOutput with no args"""
72 rich = capture.RichOutput()
76 rich = capture.RichOutput()
73 for method, mime in _mime_map.items():
77 method, mime = method_mime
74 yield nt.assert_equal, getattr(rich, method)(), None
78 nt.assert_equal(getattr(rich, method)(), None)
75
79
76 def test_rich_output():
80 def test_rich_output():
77 """test RichOutput basics"""
81 """test RichOutput basics"""
78 data = basic_data
82 data = basic_data
79 metadata = basic_metadata
83 metadata = basic_metadata
80 rich = capture.RichOutput(data=data, metadata=metadata)
84 rich = capture.RichOutput(data=data, metadata=metadata)
81 yield nt.assert_equal, rich._repr_html_(), data['text/html']
85 nt.assert_equal(rich._repr_html_(), data["text/html"])
82 yield nt.assert_equal, rich._repr_png_(), (data['image/png'], metadata['image/png'])
86 nt.assert_equal(rich._repr_png_(), (data["image/png"], metadata["image/png"]))
83 yield nt.assert_equal, rich._repr_latex_(), None
87 nt.assert_equal(rich._repr_latex_(), None)
84 yield nt.assert_equal, rich._repr_javascript_(), None
88 nt.assert_equal(rich._repr_javascript_(), None)
85 yield nt.assert_equal, rich._repr_svg_(), None
89 nt.assert_equal(rich._repr_svg_(), None)
86
90
87 def test_rich_output_no_metadata():
91
92 @skip_iptest_but_not_pytest
93 @pytest.mark.parametrize("method_mime", _mime_map.items())
94 def test_rich_output_no_metadata(method_mime):
88 """test RichOutput with no metadata"""
95 """test RichOutput with no metadata"""
89 data = full_data
96 data = full_data
90 rich = capture.RichOutput(data=data)
97 rich = capture.RichOutput(data=data)
91 for method, mime in _mime_map.items():
98 method, mime = method_mime
92 yield nt.assert_equal, getattr(rich, method)(), data[mime]
99 nt.assert_equal(getattr(rich, method)(), data[mime])
100
93
101
94 def test_rich_output_metadata():
102 @skip_iptest_but_not_pytest
103 @pytest.mark.parametrize("method_mime", _mime_map.items())
104 def test_rich_output_metadata(method_mime):
95 """test RichOutput with metadata"""
105 """test RichOutput with metadata"""
96 data = full_data
106 data = full_data
97 metadata = full_metadata
107 metadata = full_metadata
98 rich = capture.RichOutput(data=data, metadata=metadata)
108 rich = capture.RichOutput(data=data, metadata=metadata)
99 for method, mime in _mime_map.items():
109 method, mime = method_mime
100 yield nt.assert_equal, getattr(rich, method)(), (data[mime], metadata[mime])
110 nt.assert_equal(getattr(rich, method)(), (data[mime], metadata[mime]))
101
111
102 def test_rich_output_display():
112 def test_rich_output_display():
103 """test RichOutput.display
113 """test RichOutput.display
104
114
105 This is a bit circular, because we are actually using the capture code we are testing
115 This is a bit circular, because we are actually using the capture code we are testing
106 to test itself.
116 to test itself.
107 """
117 """
108 data = full_data
118 data = full_data
109 rich = capture.RichOutput(data=data)
119 rich = capture.RichOutput(data=data)
110 with capture.capture_output() as cap:
120 with capture.capture_output() as cap:
111 rich.display()
121 rich.display()
112 yield nt.assert_equal, len(cap.outputs), 1
122 nt.assert_equal(len(cap.outputs), 1)
113 rich2 = cap.outputs[0]
123 rich2 = cap.outputs[0]
114 yield nt.assert_equal, rich2.data, rich.data
124 nt.assert_equal(rich2.data, rich.data)
115 yield nt.assert_equal, rich2.metadata, rich.metadata
125 nt.assert_equal(rich2.metadata, rich.metadata)
116
126
117 def test_capture_output():
127 def test_capture_output():
118 """capture_output works"""
128 """capture_output works"""
119 rich = capture.RichOutput(data=full_data)
129 rich = capture.RichOutput(data=full_data)
120 with capture.capture_output() as cap:
130 with capture.capture_output() as cap:
121 print(hello_stdout, end="")
131 print(hello_stdout, end="")
122 print(hello_stderr, end="", file=sys.stderr)
132 print(hello_stderr, end="", file=sys.stderr)
123 rich.display()
133 rich.display()
124 yield nt.assert_equal, hello_stdout, cap.stdout
134 nt.assert_equal(hello_stdout, cap.stdout)
125 yield nt.assert_equal, hello_stderr, cap.stderr
135 nt.assert_equal(hello_stderr, cap.stderr)
136
126
137
127 def test_capture_output_no_stdout():
138 def test_capture_output_no_stdout():
128 """test capture_output(stdout=False)"""
139 """test capture_output(stdout=False)"""
129 rich = capture.RichOutput(data=full_data)
140 rich = capture.RichOutput(data=full_data)
130 with capture.capture_output(stdout=False) as cap:
141 with capture.capture_output(stdout=False) as cap:
131 print(hello_stdout, end="")
142 print(hello_stdout, end="")
132 print(hello_stderr, end="", file=sys.stderr)
143 print(hello_stderr, end="", file=sys.stderr)
133 rich.display()
144 rich.display()
134 yield nt.assert_equal, "", cap.stdout
145 nt.assert_equal("", cap.stdout)
135 yield nt.assert_equal, hello_stderr, cap.stderr
146 nt.assert_equal(hello_stderr, cap.stderr)
136 yield nt.assert_equal, len(cap.outputs), 1
147 nt.assert_equal(len(cap.outputs), 1)
148
137
149
138 def test_capture_output_no_stderr():
150 def test_capture_output_no_stderr():
139 """test capture_output(stderr=False)"""
151 """test capture_output(stderr=False)"""
140 rich = capture.RichOutput(data=full_data)
152 rich = capture.RichOutput(data=full_data)
141 # add nested capture_output so stderr doesn't make it to nose output
153 # add nested capture_output so stderr doesn't make it to nose output
142 with capture.capture_output(), capture.capture_output(stderr=False) as cap:
154 with capture.capture_output(), capture.capture_output(stderr=False) as cap:
143 print(hello_stdout, end="")
155 print(hello_stdout, end="")
144 print(hello_stderr, end="", file=sys.stderr)
156 print(hello_stderr, end="", file=sys.stderr)
145 rich.display()
157 rich.display()
146 yield nt.assert_equal, hello_stdout, cap.stdout
158 nt.assert_equal(hello_stdout, cap.stdout)
147 yield nt.assert_equal, "", cap.stderr
159 nt.assert_equal("", cap.stderr)
148 yield nt.assert_equal, len(cap.outputs), 1
160 nt.assert_equal(len(cap.outputs), 1)
161
149
162
150 def test_capture_output_no_display():
163 def test_capture_output_no_display():
151 """test capture_output(display=False)"""
164 """test capture_output(display=False)"""
152 rich = capture.RichOutput(data=full_data)
165 rich = capture.RichOutput(data=full_data)
153 with capture.capture_output(display=False) as cap:
166 with capture.capture_output(display=False) as cap:
154 print(hello_stdout, end="")
167 print(hello_stdout, end="")
155 print(hello_stderr, end="", file=sys.stderr)
168 print(hello_stderr, end="", file=sys.stderr)
156 rich.display()
169 rich.display()
157 yield nt.assert_equal, hello_stdout, cap.stdout
170 nt.assert_equal(hello_stdout, cap.stdout)
158 yield nt.assert_equal, hello_stderr, cap.stderr
171 nt.assert_equal(hello_stderr, cap.stderr)
159 yield nt.assert_equal, cap.outputs, [] No newline at end of file
172 nt.assert_equal(cap.outputs, [])
@@ -1,78 +1,76 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Test suite for our color utilities.
2 """Test suite for our color utilities.
3
3
4 Authors
4 Authors
5 -------
5 -------
6
6
7 * Min RK
7 * Min RK
8 """
8 """
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2011 The IPython Development Team
10 # Copyright (C) 2011 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING.txt, distributed as part of this software.
13 # the file COPYING.txt, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 # third party
20 # third party
21 import nose.tools as nt
21 import nose.tools as nt
22
22
23 from IPython.testing.decorators import skip_iptest_but_not_pytest
24
23 # our own
25 # our own
24 from IPython.utils.PyColorize import Parser
26 from IPython.utils.PyColorize import Parser
25 import io
27 import io
28 import pytest
29
30
31 @pytest.fixture(scope="module", params=("Linux", "NoColor", "LightBG", "Neutral"))
32 def style(request):
33 yield request.param
26
34
27 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
28 # Test functions
36 # Test functions
29 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
30
38
31 sample = u"""
39 sample = """
32 def function(arg, *args, kwarg=True, **kwargs):
40 def function(arg, *args, kwarg=True, **kwargs):
33 '''
41 '''
34 this is docs
42 this is docs
35 '''
43 '''
36 pass is True
44 pass is True
37 False == None
45 False == None
38
46
39 with io.open(ru'unicode'):
47 with io.open(ru'unicode'):
40 raise ValueError("\n escape \r sequence")
48 raise ValueError("\n escape \r sequence")
41
49
42 print("wΔ›ird ΓΌnicoΓ°e")
50 print("wΔ›ird ΓΌnicoΓ°e")
43
51
44 class Bar(Super):
52 class Bar(Super):
45
53
46 def __init__(self):
54 def __init__(self):
47 super(Bar, self).__init__(1**2, 3^4, 5 or 6)
55 super(Bar, self).__init__(1**2, 3^4, 5 or 6)
48 """
56 """
49
57
50 def test_loop_colors():
51
52 for style in ('Linux', 'NoColor','LightBG', 'Neutral'):
53
54 def test_unicode_colorize():
55 p = Parser(style=style)
56 f1 = p.format('1/0', 'str')
57 f2 = p.format(u'1/0', 'str')
58 nt.assert_equal(f1, f2)
59
58
60 def test_parse_sample():
59 @skip_iptest_but_not_pytest
61 """and test writing to a buffer"""
60 def test_parse_sample(style):
62 buf = io.StringIO()
61 """and test writing to a buffer"""
63 p = Parser(style=style)
62 buf = io.StringIO()
64 p.format(sample, buf)
63 p = Parser(style=style)
65 buf.seek(0)
64 p.format(sample, buf)
66 f1 = buf.read()
65 buf.seek(0)
66 f1 = buf.read()
67
67
68 nt.assert_not_in('ERROR', f1)
68 nt.assert_not_in("ERROR", f1)
69
69
70 def test_parse_error():
71 p = Parser(style=style)
72 f1 = p.format(')', 'str')
73 if style != 'NoColor':
74 nt.assert_in('ERROR', f1)
75
70
76 yield test_unicode_colorize
71 @skip_iptest_but_not_pytest
77 yield test_parse_sample
72 def test_parse_error(style):
78 yield test_parse_error
73 p = Parser(style=style)
74 f1 = p.format(")", "str")
75 if style != "NoColor":
76 nt.assert_in("ERROR", f1)
@@ -1,133 +1,141 b''
1 """Tests for tokenutil"""
1 """Tests for tokenutil"""
2 # Copyright (c) IPython Development Team.
2 # Copyright (c) IPython Development Team.
3 # Distributed under the terms of the Modified BSD License.
3 # Distributed under the terms of the Modified BSD License.
4
4
5 import nose.tools as nt
5 import nose.tools as nt
6 import pytest
7 from IPython.testing.decorators import skip_iptest_but_not_pytest
6
8
7 from IPython.utils.tokenutil import token_at_cursor, line_at_cursor
9 from IPython.utils.tokenutil import token_at_cursor, line_at_cursor
8
10
9 def expect_token(expected, cell, cursor_pos):
11 def expect_token(expected, cell, cursor_pos):
10 token = token_at_cursor(cell, cursor_pos)
12 token = token_at_cursor(cell, cursor_pos)
11 offset = 0
13 offset = 0
12 for line in cell.splitlines():
14 for line in cell.splitlines():
13 if offset + len(line) >= cursor_pos:
15 if offset + len(line) >= cursor_pos:
14 break
16 break
15 else:
17 else:
16 offset += len(line)+1
18 offset += len(line)+1
17 column = cursor_pos - offset
19 column = cursor_pos - offset
18 line_with_cursor = '%s|%s' % (line[:column], line[column:])
20 line_with_cursor = '%s|%s' % (line[:column], line[column:])
19 nt.assert_equal(token, expected,
21 nt.assert_equal(token, expected,
20 "Expected %r, got %r in: %r (pos %i)" % (
22 "Expected %r, got %r in: %r (pos %i)" % (
21 expected, token, line_with_cursor, cursor_pos)
23 expected, token, line_with_cursor, cursor_pos)
22 )
24 )
23
25
24 def test_simple():
26 def test_simple():
25 cell = "foo"
27 cell = "foo"
26 for i in range(len(cell)):
28 for i in range(len(cell)):
27 expect_token("foo", cell, i)
29 expect_token("foo", cell, i)
28
30
29 def test_function():
31 def test_function():
30 cell = "foo(a=5, b='10')"
32 cell = "foo(a=5, b='10')"
31 expected = 'foo'
33 expected = 'foo'
32 # up to `foo(|a=`
34 # up to `foo(|a=`
33 for i in range(cell.find('a=') + 1):
35 for i in range(cell.find('a=') + 1):
34 expect_token("foo", cell, i)
36 expect_token("foo", cell, i)
35 # find foo after `=`
37 # find foo after `=`
36 for i in [cell.find('=') + 1, cell.rfind('=') + 1]:
38 for i in [cell.find('=') + 1, cell.rfind('=') + 1]:
37 expect_token("foo", cell, i)
39 expect_token("foo", cell, i)
38 # in between `5,|` and `|b=`
40 # in between `5,|` and `|b=`
39 for i in range(cell.find(','), cell.find('b=')):
41 for i in range(cell.find(','), cell.find('b=')):
40 expect_token("foo", cell, i)
42 expect_token("foo", cell, i)
41
43
42 def test_multiline():
44 def test_multiline():
43 cell = '\n'.join([
45 cell = '\n'.join([
44 'a = 5',
46 'a = 5',
45 'b = hello("string", there)'
47 'b = hello("string", there)'
46 ])
48 ])
47 expected = 'hello'
49 expected = 'hello'
48 start = cell.index(expected) + 1
50 start = cell.index(expected) + 1
49 for i in range(start, start + len(expected)):
51 for i in range(start, start + len(expected)):
50 expect_token(expected, cell, i)
52 expect_token(expected, cell, i)
51 expected = 'hello'
53 expected = 'hello'
52 start = cell.index(expected) + 1
54 start = cell.index(expected) + 1
53 for i in range(start, start + len(expected)):
55 for i in range(start, start + len(expected)):
54 expect_token(expected, cell, i)
56 expect_token(expected, cell, i)
55
57
56 def test_multiline_token():
58 def test_multiline_token():
57 cell = '\n'.join([
59 cell = '\n'.join([
58 '"""\n\nxxxxxxxxxx\n\n"""',
60 '"""\n\nxxxxxxxxxx\n\n"""',
59 '5, """',
61 '5, """',
60 'docstring',
62 'docstring',
61 'multiline token',
63 'multiline token',
62 '""", [',
64 '""", [',
63 '2, 3, "complicated"]',
65 '2, 3, "complicated"]',
64 'b = hello("string", there)'
66 'b = hello("string", there)'
65 ])
67 ])
66 expected = 'hello'
68 expected = 'hello'
67 start = cell.index(expected) + 1
69 start = cell.index(expected) + 1
68 for i in range(start, start + len(expected)):
70 for i in range(start, start + len(expected)):
69 expect_token(expected, cell, i)
71 expect_token(expected, cell, i)
70 expected = 'hello'
72 expected = 'hello'
71 start = cell.index(expected) + 1
73 start = cell.index(expected) + 1
72 for i in range(start, start + len(expected)):
74 for i in range(start, start + len(expected)):
73 expect_token(expected, cell, i)
75 expect_token(expected, cell, i)
74
76
75 def test_nested_call():
77 def test_nested_call():
76 cell = "foo(bar(a=5), b=10)"
78 cell = "foo(bar(a=5), b=10)"
77 expected = 'foo'
79 expected = 'foo'
78 start = cell.index('bar') + 1
80 start = cell.index('bar') + 1
79 for i in range(start, start + 3):
81 for i in range(start, start + 3):
80 expect_token(expected, cell, i)
82 expect_token(expected, cell, i)
81 expected = 'bar'
83 expected = 'bar'
82 start = cell.index('a=')
84 start = cell.index('a=')
83 for i in range(start, start + 3):
85 for i in range(start, start + 3):
84 expect_token(expected, cell, i)
86 expect_token(expected, cell, i)
85 expected = 'foo'
87 expected = 'foo'
86 start = cell.index(')') + 1
88 start = cell.index(')') + 1
87 for i in range(start, len(cell)-1):
89 for i in range(start, len(cell)-1):
88 expect_token(expected, cell, i)
90 expect_token(expected, cell, i)
89
91
90 def test_attrs():
92 def test_attrs():
91 cell = "a = obj.attr.subattr"
93 cell = "a = obj.attr.subattr"
92 expected = 'obj'
94 expected = 'obj'
93 idx = cell.find('obj') + 1
95 idx = cell.find('obj') + 1
94 for i in range(idx, idx + 3):
96 for i in range(idx, idx + 3):
95 expect_token(expected, cell, i)
97 expect_token(expected, cell, i)
96 idx = cell.find('.attr') + 2
98 idx = cell.find('.attr') + 2
97 expected = 'obj.attr'
99 expected = 'obj.attr'
98 for i in range(idx, idx + 4):
100 for i in range(idx, idx + 4):
99 expect_token(expected, cell, i)
101 expect_token(expected, cell, i)
100 idx = cell.find('.subattr') + 2
102 idx = cell.find('.subattr') + 2
101 expected = 'obj.attr.subattr'
103 expected = 'obj.attr.subattr'
102 for i in range(idx, len(cell)):
104 for i in range(idx, len(cell)):
103 expect_token(expected, cell, i)
105 expect_token(expected, cell, i)
104
106
105 def test_line_at_cursor():
107 def test_line_at_cursor():
106 cell = ""
108 cell = ""
107 (line, offset) = line_at_cursor(cell, cursor_pos=11)
109 (line, offset) = line_at_cursor(cell, cursor_pos=11)
108 nt.assert_equal(line, "")
110 nt.assert_equal(line, "")
109 nt.assert_equal(offset, 0)
111 nt.assert_equal(offset, 0)
110
112
111 # The position after a newline should be the start of the following line.
113 # The position after a newline should be the start of the following line.
112 cell = "One\nTwo\n"
114 cell = "One\nTwo\n"
113 (line, offset) = line_at_cursor(cell, cursor_pos=4)
115 (line, offset) = line_at_cursor(cell, cursor_pos=4)
114 nt.assert_equal(line, "Two\n")
116 nt.assert_equal(line, "Two\n")
115 nt.assert_equal(offset, 4)
117 nt.assert_equal(offset, 4)
116
118
117 # The end of a cell should be on the last line
119 # The end of a cell should be on the last line
118 cell = "pri\npri"
120 cell = "pri\npri"
119 (line, offset) = line_at_cursor(cell, cursor_pos=7)
121 (line, offset) = line_at_cursor(cell, cursor_pos=7)
120 nt.assert_equal(line, "pri")
122 nt.assert_equal(line, "pri")
121 nt.assert_equal(offset, 4)
123 nt.assert_equal(offset, 4)
122
124
123 def test_multiline_statement():
125
126 @pytest.mark.parametrize(
127 "c, token",
128 zip(
129 list(range(16, 22)) + list(range(22, 28)),
130 ["int"] * (22 - 16) + ["map"] * (28 - 22),
131 ),
132 )
133 @skip_iptest_but_not_pytest
134 def test_multiline_statement(c, token):
124 cell = """a = (1,
135 cell = """a = (1,
125 3)
136 3)
126
137
127 int()
138 int()
128 map()
139 map()
129 """
140 """
130 for c in range(16, 22):
141 expect_token(token, cell, c)
131 yield lambda cell, c: expect_token("int", cell, c), cell, c
132 for c in range(22, 28):
133 yield lambda cell, c: expect_token("map", cell, c), cell, c
@@ -1,31 +1,31 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 matrix:
6 matrix:
7
7
8 - PYTHON: "C:\\Python37-x64"
8 - PYTHON: "C:\\Python37-x64"
9 PYTHON_VERSION: "3.7.x"
9 PYTHON_VERSION: "3.7.x"
10 PYTHON_ARCH: "64"
10 PYTHON_ARCH: "64"
11
11
12 - PYTHON: "C:\\Python38"
12 - PYTHON: "C:\\Python38"
13 PYTHON_VERSION: "3.8.x"
13 PYTHON_VERSION: "3.8.x"
14 PYTHON_ARCH: "32"
14 PYTHON_ARCH: "32"
15
15
16 - PYTHON: "C:\\Python38-x64"
16 - PYTHON: "C:\\Python38-x64"
17 PYTHON_VERSION: "3.8.x"
17 PYTHON_VERSION: "3.8.x"
18 PYTHON_ARCH: "64"
18 PYTHON_ARCH: "64"
19
19
20 init:
20 init:
21 - "ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH%"
21 - "ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH%"
22
22
23 install:
23 install:
24 - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
24 - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
25 - "%CMD_IN_ENV% python -m pip install --upgrade setuptools pip"
25 - "%CMD_IN_ENV% python -m pip install --upgrade setuptools pip"
26 - "%CMD_IN_ENV% pip install nose coverage"
26 - "%CMD_IN_ENV% pip install nose coverage pytest"
27 - "%CMD_IN_ENV% pip install .[test]"
27 - "%CMD_IN_ENV% pip install .[test]"
28 - "%CMD_IN_ENV% mkdir results"
28 - "%CMD_IN_ENV% mkdir results"
29 - "%CMD_IN_ENV% cd results"
29 - "%CMD_IN_ENV% cd results"
30 test_script:
30 test_script:
31 - "%CMD_IN_ENV% iptest --coverage xml"
31 - "%CMD_IN_ENV% iptest --coverage xml"
General Comments 0
You need to be logged in to leave comments. Login now