test_rcparams.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  1. import copy
  2. import os
  3. from pathlib import Path
  4. import subprocess
  5. import sys
  6. from unittest import mock
  7. from cycler import cycler, Cycler
  8. import pytest
  9. import matplotlib as mpl
  10. from matplotlib import _api, _c_internal_utils
  11. import matplotlib.pyplot as plt
  12. import matplotlib.colors as mcolors
  13. import numpy as np
  14. from matplotlib.rcsetup import (
  15. validate_bool,
  16. validate_color,
  17. validate_colorlist,
  18. _validate_color_or_linecolor,
  19. validate_cycler,
  20. validate_float,
  21. validate_fontstretch,
  22. validate_fontweight,
  23. validate_hatch,
  24. validate_hist_bins,
  25. validate_int,
  26. validate_markevery,
  27. validate_stringlist,
  28. _validate_linestyle,
  29. _listify_validator)
  30. def test_rcparams(tmpdir):
  31. mpl.rc('text', usetex=False)
  32. mpl.rc('lines', linewidth=22)
  33. usetex = mpl.rcParams['text.usetex']
  34. linewidth = mpl.rcParams['lines.linewidth']
  35. rcpath = Path(tmpdir) / 'test_rcparams.rc'
  36. rcpath.write_text('lines.linewidth: 33', encoding='utf-8')
  37. # test context given dictionary
  38. with mpl.rc_context(rc={'text.usetex': not usetex}):
  39. assert mpl.rcParams['text.usetex'] == (not usetex)
  40. assert mpl.rcParams['text.usetex'] == usetex
  41. # test context given filename (mpl.rc sets linewidth to 33)
  42. with mpl.rc_context(fname=rcpath):
  43. assert mpl.rcParams['lines.linewidth'] == 33
  44. assert mpl.rcParams['lines.linewidth'] == linewidth
  45. # test context given filename and dictionary
  46. with mpl.rc_context(fname=rcpath, rc={'lines.linewidth': 44}):
  47. assert mpl.rcParams['lines.linewidth'] == 44
  48. assert mpl.rcParams['lines.linewidth'] == linewidth
  49. # test context as decorator (and test reusability, by calling func twice)
  50. @mpl.rc_context({'lines.linewidth': 44})
  51. def func():
  52. assert mpl.rcParams['lines.linewidth'] == 44
  53. func()
  54. func()
  55. # test rc_file
  56. mpl.rc_file(rcpath)
  57. assert mpl.rcParams['lines.linewidth'] == 33
  58. def test_RcParams_class():
  59. rc = mpl.RcParams({'font.cursive': ['Apple Chancery',
  60. 'Textile',
  61. 'Zapf Chancery',
  62. 'cursive'],
  63. 'font.family': 'sans-serif',
  64. 'font.weight': 'normal',
  65. 'font.size': 12})
  66. expected_repr = """
  67. RcParams({'font.cursive': ['Apple Chancery',
  68. 'Textile',
  69. 'Zapf Chancery',
  70. 'cursive'],
  71. 'font.family': ['sans-serif'],
  72. 'font.size': 12.0,
  73. 'font.weight': 'normal'})""".lstrip()
  74. assert expected_repr == repr(rc)
  75. expected_str = """
  76. font.cursive: ['Apple Chancery', 'Textile', 'Zapf Chancery', 'cursive']
  77. font.family: ['sans-serif']
  78. font.size: 12.0
  79. font.weight: normal""".lstrip()
  80. assert expected_str == str(rc)
  81. # test the find_all functionality
  82. assert ['font.cursive', 'font.size'] == sorted(rc.find_all('i[vz]'))
  83. assert ['font.family'] == list(rc.find_all('family'))
  84. def test_rcparams_update():
  85. rc = mpl.RcParams({'figure.figsize': (3.5, 42)})
  86. bad_dict = {'figure.figsize': (3.5, 42, 1)}
  87. # make sure validation happens on input
  88. with pytest.raises(ValueError), \
  89. pytest.warns(UserWarning, match="validate"):
  90. rc.update(bad_dict)
  91. def test_rcparams_init():
  92. with pytest.raises(ValueError), \
  93. pytest.warns(UserWarning, match="validate"):
  94. mpl.RcParams({'figure.figsize': (3.5, 42, 1)})
  95. def test_nargs_cycler():
  96. from matplotlib.rcsetup import cycler as ccl
  97. with pytest.raises(TypeError, match='3 were given'):
  98. # cycler() takes 0-2 arguments.
  99. ccl(ccl(color=list('rgb')), 2, 3)
  100. def test_Bug_2543():
  101. # Test that it possible to add all values to itself / deepcopy
  102. # https://github.com/matplotlib/matplotlib/issues/2543
  103. # We filter warnings at this stage since a number of them are raised
  104. # for deprecated rcparams as they should. We don't want these in the
  105. # printed in the test suite.
  106. with _api.suppress_matplotlib_deprecation_warning():
  107. with mpl.rc_context():
  108. _copy = mpl.rcParams.copy()
  109. for key in _copy:
  110. mpl.rcParams[key] = _copy[key]
  111. with mpl.rc_context():
  112. copy.deepcopy(mpl.rcParams)
  113. with pytest.raises(ValueError):
  114. validate_bool(None)
  115. with pytest.raises(ValueError):
  116. with mpl.rc_context():
  117. mpl.rcParams['svg.fonttype'] = True
  118. legend_color_tests = [
  119. ('face', {'color': 'r'}, mcolors.to_rgba('r')),
  120. ('face', {'color': 'inherit', 'axes.facecolor': 'r'},
  121. mcolors.to_rgba('r')),
  122. ('face', {'color': 'g', 'axes.facecolor': 'r'}, mcolors.to_rgba('g')),
  123. ('edge', {'color': 'r'}, mcolors.to_rgba('r')),
  124. ('edge', {'color': 'inherit', 'axes.edgecolor': 'r'},
  125. mcolors.to_rgba('r')),
  126. ('edge', {'color': 'g', 'axes.facecolor': 'r'}, mcolors.to_rgba('g'))
  127. ]
  128. legend_color_test_ids = [
  129. 'same facecolor',
  130. 'inherited facecolor',
  131. 'different facecolor',
  132. 'same edgecolor',
  133. 'inherited edgecolor',
  134. 'different facecolor',
  135. ]
  136. @pytest.mark.parametrize('color_type, param_dict, target', legend_color_tests,
  137. ids=legend_color_test_ids)
  138. def test_legend_colors(color_type, param_dict, target):
  139. param_dict[f'legend.{color_type}color'] = param_dict.pop('color')
  140. get_func = f'get_{color_type}color'
  141. with mpl.rc_context(param_dict):
  142. _, ax = plt.subplots()
  143. ax.plot(range(3), label='test')
  144. leg = ax.legend()
  145. assert getattr(leg.legendPatch, get_func)() == target
  146. def test_mfc_rcparams():
  147. mpl.rcParams['lines.markerfacecolor'] = 'r'
  148. ln = mpl.lines.Line2D([1, 2], [1, 2])
  149. assert ln.get_markerfacecolor() == 'r'
  150. def test_mec_rcparams():
  151. mpl.rcParams['lines.markeredgecolor'] = 'r'
  152. ln = mpl.lines.Line2D([1, 2], [1, 2])
  153. assert ln.get_markeredgecolor() == 'r'
  154. def test_axes_titlecolor_rcparams():
  155. mpl.rcParams['axes.titlecolor'] = 'r'
  156. _, ax = plt.subplots()
  157. title = ax.set_title("Title")
  158. assert title.get_color() == 'r'
  159. def test_Issue_1713(tmpdir):
  160. rcpath = Path(tmpdir) / 'test_rcparams.rc'
  161. rcpath.write_text('timezone: UTC', encoding='utf-8')
  162. with mock.patch('locale.getpreferredencoding', return_value='UTF-32-BE'):
  163. rc = mpl.rc_params_from_file(rcpath, True, False)
  164. assert rc.get('timezone') == 'UTC'
  165. def test_animation_frame_formats():
  166. # Animation frame_format should allow any of the following
  167. # if any of these are not allowed, an exception will be raised
  168. # test for gh issue #17908
  169. for fmt in ['png', 'jpeg', 'tiff', 'raw', 'rgba', 'ppm',
  170. 'sgi', 'bmp', 'pbm', 'svg']:
  171. mpl.rcParams['animation.frame_format'] = fmt
  172. def generate_validator_testcases(valid):
  173. validation_tests = (
  174. {'validator': validate_bool,
  175. 'success': (*((_, True) for _ in
  176. ('t', 'y', 'yes', 'on', 'true', '1', 1, True)),
  177. *((_, False) for _ in
  178. ('f', 'n', 'no', 'off', 'false', '0', 0, False))),
  179. 'fail': ((_, ValueError)
  180. for _ in ('aardvark', 2, -1, [], ))
  181. },
  182. {'validator': validate_stringlist,
  183. 'success': (('', []),
  184. ('a,b', ['a', 'b']),
  185. ('aardvark', ['aardvark']),
  186. ('aardvark, ', ['aardvark']),
  187. ('aardvark, ,', ['aardvark']),
  188. (['a', 'b'], ['a', 'b']),
  189. (('a', 'b'), ['a', 'b']),
  190. (iter(['a', 'b']), ['a', 'b']),
  191. (np.array(['a', 'b']), ['a', 'b']),
  192. ),
  193. 'fail': ((set(), ValueError),
  194. (1, ValueError),
  195. )
  196. },
  197. {'validator': _listify_validator(validate_int, n=2),
  198. 'success': ((_, [1, 2])
  199. for _ in ('1, 2', [1.5, 2.5], [1, 2],
  200. (1, 2), np.array((1, 2)))),
  201. 'fail': ((_, ValueError)
  202. for _ in ('aardvark', ('a', 1),
  203. (1, 2, 3)
  204. ))
  205. },
  206. {'validator': _listify_validator(validate_float, n=2),
  207. 'success': ((_, [1.5, 2.5])
  208. for _ in ('1.5, 2.5', [1.5, 2.5], [1.5, 2.5],
  209. (1.5, 2.5), np.array((1.5, 2.5)))),
  210. 'fail': ((_, ValueError)
  211. for _ in ('aardvark', ('a', 1), (1, 2, 3), (None, ), None))
  212. },
  213. {'validator': validate_cycler,
  214. 'success': (('cycler("color", "rgb")',
  215. cycler("color", 'rgb')),
  216. (cycler('linestyle', ['-', '--']),
  217. cycler('linestyle', ['-', '--'])),
  218. ("""(cycler("color", ["r", "g", "b"]) +
  219. cycler("mew", [2, 3, 5]))""",
  220. (cycler("color", 'rgb') +
  221. cycler("markeredgewidth", [2, 3, 5]))),
  222. ("cycler(c='rgb', lw=[1, 2, 3])",
  223. cycler('color', 'rgb') + cycler('linewidth', [1, 2, 3])),
  224. ("cycler('c', 'rgb') * cycler('linestyle', ['-', '--'])",
  225. (cycler('color', 'rgb') *
  226. cycler('linestyle', ['-', '--']))),
  227. (cycler('ls', ['-', '--']),
  228. cycler('linestyle', ['-', '--'])),
  229. (cycler(mew=[2, 5]),
  230. cycler('markeredgewidth', [2, 5])),
  231. ),
  232. # This is *so* incredibly important: validate_cycler() eval's
  233. # an arbitrary string! I think I have it locked down enough,
  234. # and that is what this is testing.
  235. # TODO: Note that these tests are actually insufficient, as it may
  236. # be that they raised errors, but still did an action prior to
  237. # raising the exception. We should devise some additional tests
  238. # for that...
  239. 'fail': ((4, ValueError), # Gotta be a string or Cycler object
  240. ('cycler("bleh, [])', ValueError), # syntax error
  241. ('Cycler("linewidth", [1, 2, 3])',
  242. ValueError), # only 'cycler()' function is allowed
  243. # do not allow dunder in string literals
  244. ("cycler('c', [j.__class__(j) for j in ['r', 'b']])",
  245. ValueError),
  246. ("cycler('c', [j. __class__(j) for j in ['r', 'b']])",
  247. ValueError),
  248. ("cycler('c', [j.\t__class__(j) for j in ['r', 'b']])",
  249. ValueError),
  250. ("cycler('c', [j.\u000c__class__(j) for j in ['r', 'b']])",
  251. ValueError),
  252. ("cycler('c', [j.__class__(j).lower() for j in ['r', 'b']])",
  253. ValueError),
  254. ('1 + 2', ValueError), # doesn't produce a Cycler object
  255. ('os.system("echo Gotcha")', ValueError), # os not available
  256. ('import os', ValueError), # should not be able to import
  257. ('def badjuju(a): return a; badjuju(cycler("color", "rgb"))',
  258. ValueError), # Should not be able to define anything
  259. # even if it does return a cycler
  260. ('cycler("waka", [1, 2, 3])', ValueError), # not a property
  261. ('cycler(c=[1, 2, 3])', ValueError), # invalid values
  262. ("cycler(lw=['a', 'b', 'c'])", ValueError), # invalid values
  263. (cycler('waka', [1, 3, 5]), ValueError), # not a property
  264. (cycler('color', ['C1', 'r', 'g']), ValueError) # no CN
  265. )
  266. },
  267. {'validator': validate_hatch,
  268. 'success': (('--|', '--|'), ('\\oO', '\\oO'),
  269. ('/+*/.x', '/+*/.x'), ('', '')),
  270. 'fail': (('--_', ValueError),
  271. (8, ValueError),
  272. ('X', ValueError)),
  273. },
  274. {'validator': validate_colorlist,
  275. 'success': (('r,g,b', ['r', 'g', 'b']),
  276. (['r', 'g', 'b'], ['r', 'g', 'b']),
  277. ('r, ,', ['r']),
  278. (['', 'g', 'blue'], ['g', 'blue']),
  279. ([np.array([1, 0, 0]), np.array([0, 1, 0])],
  280. np.array([[1, 0, 0], [0, 1, 0]])),
  281. (np.array([[1, 0, 0], [0, 1, 0]]),
  282. np.array([[1, 0, 0], [0, 1, 0]])),
  283. ),
  284. 'fail': (('fish', ValueError),
  285. ),
  286. },
  287. {'validator': validate_color,
  288. 'success': (('None', 'none'),
  289. ('none', 'none'),
  290. ('AABBCC', '#AABBCC'), # RGB hex code
  291. ('AABBCC00', '#AABBCC00'), # RGBA hex code
  292. ('tab:blue', 'tab:blue'), # named color
  293. ('C12', 'C12'), # color from cycle
  294. ('(0, 1, 0)', (0.0, 1.0, 0.0)), # RGB tuple
  295. ((0, 1, 0), (0, 1, 0)), # non-string version
  296. ('(0, 1, 0, 1)', (0.0, 1.0, 0.0, 1.0)), # RGBA tuple
  297. ((0, 1, 0, 1), (0, 1, 0, 1)), # non-string version
  298. ),
  299. 'fail': (('tab:veryblue', ValueError), # invalid name
  300. ('(0, 1)', ValueError), # tuple with length < 3
  301. ('(0, 1, 0, 1, 0)', ValueError), # tuple with length > 4
  302. ('(0, 1, none)', ValueError), # cannot cast none to float
  303. ('(0, 1, "0.5")', ValueError), # last one not a float
  304. ),
  305. },
  306. {'validator': _validate_color_or_linecolor,
  307. 'success': (('linecolor', 'linecolor'),
  308. ('markerfacecolor', 'markerfacecolor'),
  309. ('mfc', 'markerfacecolor'),
  310. ('markeredgecolor', 'markeredgecolor'),
  311. ('mec', 'markeredgecolor')
  312. ),
  313. 'fail': (('line', ValueError),
  314. ('marker', ValueError)
  315. )
  316. },
  317. {'validator': validate_hist_bins,
  318. 'success': (('auto', 'auto'),
  319. ('fd', 'fd'),
  320. ('10', 10),
  321. ('1, 2, 3', [1, 2, 3]),
  322. ([1, 2, 3], [1, 2, 3]),
  323. (np.arange(15), np.arange(15))
  324. ),
  325. 'fail': (('aardvark', ValueError),
  326. )
  327. },
  328. {'validator': validate_markevery,
  329. 'success': ((None, None),
  330. (1, 1),
  331. (0.1, 0.1),
  332. ((1, 1), (1, 1)),
  333. ((0.1, 0.1), (0.1, 0.1)),
  334. ([1, 2, 3], [1, 2, 3]),
  335. (slice(2), slice(None, 2, None)),
  336. (slice(1, 2, 3), slice(1, 2, 3))
  337. ),
  338. 'fail': (((1, 2, 3), TypeError),
  339. ([1, 2, 0.3], TypeError),
  340. (['a', 2, 3], TypeError),
  341. ([1, 2, 'a'], TypeError),
  342. ((0.1, 0.2, 0.3), TypeError),
  343. ((0.1, 2, 3), TypeError),
  344. ((1, 0.2, 0.3), TypeError),
  345. ((1, 0.1), TypeError),
  346. ((0.1, 1), TypeError),
  347. (('abc'), TypeError),
  348. ((1, 'a'), TypeError),
  349. ((0.1, 'b'), TypeError),
  350. (('a', 1), TypeError),
  351. (('a', 0.1), TypeError),
  352. ('abc', TypeError),
  353. ('a', TypeError),
  354. (object(), TypeError)
  355. )
  356. },
  357. {'validator': _validate_linestyle,
  358. 'success': (('-', '-'), ('solid', 'solid'),
  359. ('--', '--'), ('dashed', 'dashed'),
  360. ('-.', '-.'), ('dashdot', 'dashdot'),
  361. (':', ':'), ('dotted', 'dotted'),
  362. ('', ''), (' ', ' '),
  363. ('None', 'none'), ('none', 'none'),
  364. ('DoTtEd', 'dotted'), # case-insensitive
  365. ('1, 3', (0, (1, 3))),
  366. ([1.23, 456], (0, [1.23, 456.0])),
  367. ([1, 2, 3, 4], (0, [1.0, 2.0, 3.0, 4.0])),
  368. ((0, [1, 2]), (0, [1, 2])),
  369. ((-1, [1, 2]), (-1, [1, 2])),
  370. ),
  371. 'fail': (('aardvark', ValueError), # not a valid string
  372. (b'dotted', ValueError),
  373. ('dotted'.encode('utf-16'), ValueError),
  374. ([1, 2, 3], ValueError), # sequence with odd length
  375. (1.23, ValueError), # not a sequence
  376. (("a", [1, 2]), ValueError), # wrong explicit offset
  377. ((None, [1, 2]), ValueError), # wrong explicit offset
  378. ((1, [1, 2, 3]), ValueError), # odd length sequence
  379. (([1, 2], 1), ValueError), # inverted offset/onoff
  380. )
  381. },
  382. )
  383. for validator_dict in validation_tests:
  384. validator = validator_dict['validator']
  385. if valid:
  386. for arg, target in validator_dict['success']:
  387. yield validator, arg, target
  388. else:
  389. for arg, error_type in validator_dict['fail']:
  390. yield validator, arg, error_type
  391. @pytest.mark.parametrize('validator, arg, target',
  392. generate_validator_testcases(True))
  393. def test_validator_valid(validator, arg, target):
  394. res = validator(arg)
  395. if isinstance(target, np.ndarray):
  396. np.testing.assert_equal(res, target)
  397. elif not isinstance(target, Cycler):
  398. assert res == target
  399. else:
  400. # Cyclers can't simply be asserted equal. They don't implement __eq__
  401. assert list(res) == list(target)
  402. @pytest.mark.parametrize('validator, arg, exception_type',
  403. generate_validator_testcases(False))
  404. def test_validator_invalid(validator, arg, exception_type):
  405. with pytest.raises(exception_type):
  406. validator(arg)
  407. @pytest.mark.parametrize('weight, parsed_weight', [
  408. ('bold', 'bold'),
  409. ('BOLD', ValueError), # weight is case-sensitive
  410. (100, 100),
  411. ('100', 100),
  412. (np.array(100), 100),
  413. # fractional fontweights are not defined. This should actually raise a
  414. # ValueError, but historically did not.
  415. (20.6, 20),
  416. ('20.6', ValueError),
  417. ([100], ValueError),
  418. ])
  419. def test_validate_fontweight(weight, parsed_weight):
  420. if parsed_weight is ValueError:
  421. with pytest.raises(ValueError):
  422. validate_fontweight(weight)
  423. else:
  424. assert validate_fontweight(weight) == parsed_weight
  425. @pytest.mark.parametrize('stretch, parsed_stretch', [
  426. ('expanded', 'expanded'),
  427. ('EXPANDED', ValueError), # stretch is case-sensitive
  428. (100, 100),
  429. ('100', 100),
  430. (np.array(100), 100),
  431. # fractional fontweights are not defined. This should actually raise a
  432. # ValueError, but historically did not.
  433. (20.6, 20),
  434. ('20.6', ValueError),
  435. ([100], ValueError),
  436. ])
  437. def test_validate_fontstretch(stretch, parsed_stretch):
  438. if parsed_stretch is ValueError:
  439. with pytest.raises(ValueError):
  440. validate_fontstretch(stretch)
  441. else:
  442. assert validate_fontstretch(stretch) == parsed_stretch
  443. def test_keymaps():
  444. key_list = [k for k in mpl.rcParams if 'keymap' in k]
  445. for k in key_list:
  446. assert isinstance(mpl.rcParams[k], list)
  447. def test_no_backend_reset_rccontext():
  448. assert mpl.rcParams['backend'] != 'module://aardvark'
  449. with mpl.rc_context():
  450. mpl.rcParams['backend'] = 'module://aardvark'
  451. assert mpl.rcParams['backend'] == 'module://aardvark'
  452. def test_rcparams_reset_after_fail():
  453. # There was previously a bug that meant that if rc_context failed and
  454. # raised an exception due to issues in the supplied rc parameters, the
  455. # global rc parameters were left in a modified state.
  456. with mpl.rc_context(rc={'text.usetex': False}):
  457. assert mpl.rcParams['text.usetex'] is False
  458. with pytest.raises(KeyError):
  459. with mpl.rc_context(rc={'text.usetex': True, 'test.blah': True}):
  460. pass
  461. assert mpl.rcParams['text.usetex'] is False
  462. @pytest.mark.skipif(sys.platform != "linux", reason="Linux only")
  463. def test_backend_fallback_headless(tmpdir):
  464. env = {**os.environ,
  465. "DISPLAY": "", "WAYLAND_DISPLAY": "",
  466. "MPLBACKEND": "", "MPLCONFIGDIR": str(tmpdir)}
  467. with pytest.raises(subprocess.CalledProcessError):
  468. subprocess.run(
  469. [sys.executable, "-c",
  470. "import matplotlib;"
  471. "matplotlib.use('tkagg');"
  472. "import matplotlib.pyplot;"
  473. "matplotlib.pyplot.plot(42);"
  474. ],
  475. env=env, check=True, stderr=subprocess.DEVNULL)
  476. @pytest.mark.skipif(
  477. sys.platform == "linux" and not _c_internal_utils.display_is_valid(),
  478. reason="headless")
  479. def test_backend_fallback_headful(tmpdir):
  480. pytest.importorskip("tkinter")
  481. env = {**os.environ, "MPLBACKEND": "", "MPLCONFIGDIR": str(tmpdir)}
  482. backend = subprocess.check_output(
  483. [sys.executable, "-c",
  484. "import matplotlib as mpl; "
  485. "sentinel = mpl.rcsetup._auto_backend_sentinel; "
  486. # Check that access on another instance does not resolve the sentinel.
  487. "assert mpl.RcParams({'backend': sentinel})['backend'] == sentinel; "
  488. "assert mpl.rcParams._get('backend') == sentinel; "
  489. "import matplotlib.pyplot; "
  490. "print(matplotlib.get_backend())"],
  491. env=env, text=True)
  492. # The actual backend will depend on what's installed, but at least tkagg is
  493. # present.
  494. assert backend.strip().lower() != "agg"
  495. def test_deprecation(monkeypatch):
  496. monkeypatch.setitem(
  497. mpl._deprecated_map, "patch.linewidth",
  498. ("0.0", "axes.linewidth", lambda old: 2 * old, lambda new: new / 2))
  499. with pytest.warns(mpl.MatplotlibDeprecationWarning):
  500. assert mpl.rcParams["patch.linewidth"] \
  501. == mpl.rcParams["axes.linewidth"] / 2
  502. with pytest.warns(mpl.MatplotlibDeprecationWarning):
  503. mpl.rcParams["patch.linewidth"] = 1
  504. assert mpl.rcParams["axes.linewidth"] == 2
  505. monkeypatch.setitem(
  506. mpl._deprecated_ignore_map, "patch.edgecolor",
  507. ("0.0", "axes.edgecolor"))
  508. with pytest.warns(mpl.MatplotlibDeprecationWarning):
  509. assert mpl.rcParams["patch.edgecolor"] \
  510. == mpl.rcParams["axes.edgecolor"]
  511. with pytest.warns(mpl.MatplotlibDeprecationWarning):
  512. mpl.rcParams["patch.edgecolor"] = "#abcd"
  513. assert mpl.rcParams["axes.edgecolor"] != "#abcd"
  514. monkeypatch.setitem(
  515. mpl._deprecated_ignore_map, "patch.force_edgecolor",
  516. ("0.0", None))
  517. with pytest.warns(mpl.MatplotlibDeprecationWarning):
  518. assert mpl.rcParams["patch.force_edgecolor"] is None
  519. monkeypatch.setitem(
  520. mpl._deprecated_remain_as_none, "svg.hashsalt",
  521. ("0.0",))
  522. with pytest.warns(mpl.MatplotlibDeprecationWarning):
  523. mpl.rcParams["svg.hashsalt"] = "foobar"
  524. assert mpl.rcParams["svg.hashsalt"] == "foobar" # Doesn't warn.
  525. mpl.rcParams["svg.hashsalt"] = None # Doesn't warn.
  526. mpl.rcParams.update(mpl.rcParams.copy()) # Doesn't warn.
  527. # Note that the warning suppression actually arises from the
  528. # iteration over the updater rcParams being protected by
  529. # suppress_matplotlib_deprecation_warning, rather than any explicit check.
  530. @pytest.mark.parametrize("value", [
  531. "best",
  532. 1,
  533. "1",
  534. (0.9, .7),
  535. (-0.9, .7),
  536. "(0.9, .7)"
  537. ])
  538. def test_rcparams_legend_loc(value):
  539. # rcParams['legend.loc'] should allow any of the following formats.
  540. # if any of these are not allowed, an exception will be raised
  541. # test for gh issue #22338
  542. mpl.rcParams["legend.loc"] = value
  543. @pytest.mark.parametrize("value", [
  544. "best",
  545. 1,
  546. (0.9, .7),
  547. (-0.9, .7),
  548. ])
  549. def test_rcparams_legend_loc_from_file(tmpdir, value):
  550. # rcParams['legend.loc'] should be settable from matplotlibrc.
  551. # if any of these are not allowed, an exception will be raised.
  552. # test for gh issue #22338
  553. rc_path = tmpdir.join("matplotlibrc")
  554. rc_path.write(f"legend.loc: {value}")
  555. with mpl.rc_context(fname=rc_path):
  556. assert mpl.rcParams["legend.loc"] == value