1import difflib
2import subprocess
3import sys
4from pathlib import Path
5
6import pytest
7
8import matplotlib as mpl
9from matplotlib import pyplot as plt
10from matplotlib.cbook import MatplotlibDeprecationWarning
11
12
13def test_pyplot_up_to_date(tmpdir):
14    gen_script = Path(mpl.__file__).parents[2] / "tools/boilerplate.py"
15    if not gen_script.exists():
16        pytest.skip("boilerplate.py not found")
17    orig_contents = Path(plt.__file__).read_text()
18    plt_file = tmpdir.join('pyplot.py')
19    plt_file.write_text(orig_contents, 'utf-8')
20
21    subprocess.run([sys.executable, str(gen_script), str(plt_file)],
22                   check=True)
23    new_contents = plt_file.read_text('utf-8')
24
25    if orig_contents != new_contents:
26        diff_msg = '\n'.join(
27            difflib.unified_diff(
28                orig_contents.split('\n'), new_contents.split('\n'),
29                fromfile='found pyplot.py',
30                tofile='expected pyplot.py',
31                n=0, lineterm=''))
32        pytest.fail(
33            "pyplot.py is not up-to-date. Please run "
34            "'python tools/boilerplate.py' to update pyplot.py. "
35            "This needs to be done from an environment where your "
36            "current working copy is installed (e.g. 'pip install -e'd). "
37            "Here is a diff of unexpected differences:\n%s" % diff_msg
38        )
39
40
41def test_copy_docstring_and_deprecators(recwarn):
42    @mpl._api.rename_parameter("(version)", "old", "new")
43    @mpl._api.make_keyword_only("(version)", "kwo")
44    def func(new, kwo=None):
45        pass
46
47    @plt._copy_docstring_and_deprecators(func)
48    def wrapper_func(new, kwo=None):
49        pass
50
51    wrapper_func(None)
52    wrapper_func(new=None)
53    wrapper_func(None, kwo=None)
54    wrapper_func(new=None, kwo=None)
55    assert not recwarn
56    with pytest.warns(MatplotlibDeprecationWarning):
57        wrapper_func(old=None)
58    with pytest.warns(MatplotlibDeprecationWarning):
59        wrapper_func(None, None)
60
61
62def test_pyplot_box():
63    fig, ax = plt.subplots()
64    plt.box(False)
65    assert not ax.get_frame_on()
66    plt.box(True)
67    assert ax.get_frame_on()
68    plt.box()
69    assert not ax.get_frame_on()
70    plt.box()
71    assert ax.get_frame_on()
72
73
74def test_stackplot_smoke():
75    # Small smoke test for stackplot (see #12405)
76    plt.stackplot([1, 2, 3], [1, 2, 3])
77
78
79def test_nrows_error():
80    with pytest.raises(TypeError):
81        plt.subplot(nrows=1)
82    with pytest.raises(TypeError):
83        plt.subplot(ncols=1)
84
85
86def test_ioff():
87    plt.ion()
88    assert mpl.is_interactive()
89    with plt.ioff():
90        assert not mpl.is_interactive()
91    assert mpl.is_interactive()
92
93    plt.ioff()
94    assert not mpl.is_interactive()
95    with plt.ioff():
96        assert not mpl.is_interactive()
97    assert not mpl.is_interactive()
98
99
100def test_ion():
101    plt.ioff()
102    assert not mpl.is_interactive()
103    with plt.ion():
104        assert mpl.is_interactive()
105    assert not mpl.is_interactive()
106
107    plt.ion()
108    assert mpl.is_interactive()
109    with plt.ion():
110        assert mpl.is_interactive()
111    assert mpl.is_interactive()
112
113
114def test_nested_ion_ioff():
115    # initial state is interactive
116    plt.ion()
117
118    # mixed ioff/ion
119    with plt.ioff():
120        assert not mpl.is_interactive()
121        with plt.ion():
122            assert mpl.is_interactive()
123        assert not mpl.is_interactive()
124    assert mpl.is_interactive()
125
126    # redundant contexts
127    with plt.ioff():
128        with plt.ioff():
129            assert not mpl.is_interactive()
130    assert mpl.is_interactive()
131
132    with plt.ion():
133        plt.ioff()
134    assert mpl.is_interactive()
135
136    # initial state is not interactive
137    plt.ioff()
138
139    # mixed ioff/ion
140    with plt.ion():
141        assert mpl.is_interactive()
142        with plt.ioff():
143            assert not mpl.is_interactive()
144        assert mpl.is_interactive()
145    assert not mpl.is_interactive()
146
147    # redundant contexts
148    with plt.ion():
149        with plt.ion():
150            assert mpl.is_interactive()
151    assert not mpl.is_interactive()
152
153    with plt.ioff():
154        plt.ion()
155    assert not mpl.is_interactive()
156
157
158def test_close():
159    try:
160        plt.close(1.1)
161    except TypeError as e:
162        assert str(e) == "close() argument must be a Figure, an int, " \
163                         "a string, or None, not <class 'float'>"
164
165
166def test_subplot_reuse():
167    ax1 = plt.subplot(121)
168    assert ax1 is plt.gca()
169    ax2 = plt.subplot(122)
170    assert ax2 is plt.gca()
171    ax3 = plt.subplot(121)
172    assert ax1 is plt.gca()
173    assert ax1 is ax3
174
175
176def test_axes_kwargs():
177    # plt.axes() always creates new axes, even if axes kwargs differ.
178    plt.figure()
179    ax = plt.axes()
180    ax1 = plt.axes()
181    assert ax is not None
182    assert ax1 is not ax
183    plt.close()
184
185    plt.figure()
186    ax = plt.axes(projection='polar')
187    ax1 = plt.axes(projection='polar')
188    assert ax is not None
189    assert ax1 is not ax
190    plt.close()
191
192    plt.figure()
193    ax = plt.axes(projection='polar')
194    ax1 = plt.axes()
195    assert ax is not None
196    assert ax1.name == 'rectilinear'
197    assert ax1 is not ax
198    plt.close()
199
200
201def test_subplot_replace_projection():
202    # plt.subplot() searches for axes with the same subplot spec, and if one
203    # exists, and the kwargs match returns it, create a new one if they do not
204    fig = plt.figure()
205    ax = plt.subplot(1, 2, 1)
206    ax1 = plt.subplot(1, 2, 1)
207    ax2 = plt.subplot(1, 2, 2)
208    # This will delete ax / ax1 as they fully overlap
209    ax3 = plt.subplot(1, 2, 1, projection='polar')
210    ax4 = plt.subplot(1, 2, 1, projection='polar')
211    assert ax is not None
212    assert ax1 is ax
213    assert ax2 is not ax
214    assert ax3 is not ax
215    assert ax3 is ax4
216
217    assert ax not in fig.axes
218    assert ax2 in fig.axes
219    assert ax3 in fig.axes
220
221    assert ax.name == 'rectilinear'
222    assert ax2.name == 'rectilinear'
223    assert ax3.name == 'polar'
224
225
226def test_subplot_kwarg_collision():
227    ax1 = plt.subplot(projection='polar', theta_offset=0)
228    ax2 = plt.subplot(projection='polar', theta_offset=0)
229    assert ax1 is ax2
230    ax3 = plt.subplot(projection='polar', theta_offset=1)
231    assert ax1 is not ax3
232    assert ax1 not in plt.gcf().axes
233
234
235def test_gca_kwargs():
236    # plt.gca() returns an existing axes, unless there were no axes.
237    plt.figure()
238    ax = plt.gca()
239    ax1 = plt.gca()
240    assert ax is not None
241    assert ax1 is ax
242    plt.close()
243
244    # plt.gca() raises a DeprecationWarning if called with kwargs.
245    plt.figure()
246    with pytest.warns(
247            MatplotlibDeprecationWarning,
248            match=r'Calling gca\(\) with keyword arguments was deprecated'):
249        ax = plt.gca(projection='polar')
250    ax1 = plt.gca()
251    assert ax is not None
252    assert ax1 is ax
253    assert ax1.name == 'polar'
254    plt.close()
255
256    # plt.gca() ignores keyword arguments if an axes already exists.
257    plt.figure()
258    ax = plt.gca()
259    with pytest.warns(
260            MatplotlibDeprecationWarning,
261            match=r'Calling gca\(\) with keyword arguments was deprecated'):
262        ax1 = plt.gca(projection='polar')
263    assert ax is not None
264    assert ax1 is ax
265    assert ax1.name == 'rectilinear'
266    plt.close()
267
268
269def test_subplot_projection_reuse():
270    # create an axes
271    ax1 = plt.subplot(111)
272    # check that it is current
273    assert ax1 is plt.gca()
274    # make sure we get it back if we ask again
275    assert ax1 is plt.subplot(111)
276    # create a polar plot
277    ax2 = plt.subplot(111, projection='polar')
278    assert ax2 is plt.gca()
279    # this should have deleted the first axes
280    assert ax1 not in plt.gcf().axes
281    # assert we get it back if no extra parameters passed
282    assert ax2 is plt.subplot(111)
283    # now check explicitly setting the projection to rectilinear
284    # makes a new axes
285    ax3 = plt.subplot(111, projection='rectilinear')
286    assert ax3 is plt.gca()
287    assert ax3 is not ax2
288    assert ax2 not in plt.gcf().axes
289
290
291def test_subplot_polar_normalization():
292    ax1 = plt.subplot(111, projection='polar')
293    ax2 = plt.subplot(111, polar=True)
294    ax3 = plt.subplot(111, polar=True, projection='polar')
295    assert ax1 is ax2
296    assert ax1 is ax3
297
298    with pytest.raises(ValueError,
299                       match="polar=True, yet projection='3d'"):
300        ax2 = plt.subplot(111, polar=True, projection='3d')
301
302
303def test_subplot_change_projection():
304    ax = plt.subplot()
305    projections = ('aitoff', 'hammer', 'lambert', 'mollweide',
306                   'polar', 'rectilinear', '3d')
307    for proj in projections:
308        ax_next = plt.subplot(projection=proj)
309        assert ax_next is plt.subplot()
310        assert ax_next.name == proj
311        assert ax is not ax_next
312        ax = ax_next
313