1from collections import OrderedDict 2from contextlib import contextmanager 3import gc 4from pathlib import Path 5from tempfile import TemporaryDirectory 6import sys 7 8import pytest 9 10import matplotlib as mpl 11from matplotlib import pyplot as plt, style 12from matplotlib.style.core import USER_LIBRARY_PATHS, STYLE_EXTENSION 13 14 15PARAM = 'image.cmap' 16VALUE = 'pink' 17DUMMY_SETTINGS = {PARAM: VALUE} 18 19 20@contextmanager 21def temp_style(style_name, settings=None): 22 """Context manager to create a style sheet in a temporary directory.""" 23 if not settings: 24 settings = DUMMY_SETTINGS 25 temp_file = '%s.%s' % (style_name, STYLE_EXTENSION) 26 try: 27 with TemporaryDirectory() as tmpdir: 28 # Write style settings to file in the tmpdir. 29 Path(tmpdir, temp_file).write_text( 30 "\n".join("{}: {}".format(k, v) for k, v in settings.items())) 31 # Add tmpdir to style path and reload so we can access this style. 32 USER_LIBRARY_PATHS.append(tmpdir) 33 style.reload_library() 34 yield 35 finally: 36 style.reload_library() 37 38 39def test_invalid_rc_warning_includes_filename(caplog): 40 SETTINGS = {'foo': 'bar'} 41 basename = 'basename' 42 with temp_style(basename, SETTINGS): 43 # style.reload_library() in temp_style() triggers the warning 44 pass 45 assert (len(caplog.records) == 1 46 and basename in caplog.records[0].getMessage()) 47 48 49def test_available(): 50 with temp_style('_test_', DUMMY_SETTINGS): 51 assert '_test_' in style.available 52 53 54def test_use(): 55 mpl.rcParams[PARAM] = 'gray' 56 with temp_style('test', DUMMY_SETTINGS): 57 with style.context('test'): 58 assert mpl.rcParams[PARAM] == VALUE 59 60 61def test_use_url(tmpdir): 62 path = Path(tmpdir, 'file') 63 path.write_text('axes.facecolor: adeade') 64 with temp_style('test', DUMMY_SETTINGS): 65 url = ('file:' 66 + ('///' if sys.platform == 'win32' else '') 67 + path.resolve().as_posix()) 68 with style.context(url): 69 assert mpl.rcParams['axes.facecolor'] == "#adeade" 70 71 72def test_single_path(tmpdir): 73 mpl.rcParams[PARAM] = 'gray' 74 temp_file = f'text.{STYLE_EXTENSION}' 75 path = Path(tmpdir, temp_file) 76 path.write_text(f'{PARAM} : {VALUE}') 77 with style.context(path): 78 assert mpl.rcParams[PARAM] == VALUE 79 assert mpl.rcParams[PARAM] == 'gray' 80 81 82def test_context(): 83 mpl.rcParams[PARAM] = 'gray' 84 with temp_style('test', DUMMY_SETTINGS): 85 with style.context('test'): 86 assert mpl.rcParams[PARAM] == VALUE 87 # Check that this value is reset after the exiting the context. 88 assert mpl.rcParams[PARAM] == 'gray' 89 90 91def test_context_with_dict(): 92 original_value = 'gray' 93 other_value = 'blue' 94 mpl.rcParams[PARAM] = original_value 95 with style.context({PARAM: other_value}): 96 assert mpl.rcParams[PARAM] == other_value 97 assert mpl.rcParams[PARAM] == original_value 98 99 100def test_context_with_dict_after_namedstyle(): 101 # Test dict after style name where dict modifies the same parameter. 102 original_value = 'gray' 103 other_value = 'blue' 104 mpl.rcParams[PARAM] = original_value 105 with temp_style('test', DUMMY_SETTINGS): 106 with style.context(['test', {PARAM: other_value}]): 107 assert mpl.rcParams[PARAM] == other_value 108 assert mpl.rcParams[PARAM] == original_value 109 110 111def test_context_with_dict_before_namedstyle(): 112 # Test dict before style name where dict modifies the same parameter. 113 original_value = 'gray' 114 other_value = 'blue' 115 mpl.rcParams[PARAM] = original_value 116 with temp_style('test', DUMMY_SETTINGS): 117 with style.context([{PARAM: other_value}, 'test']): 118 assert mpl.rcParams[PARAM] == VALUE 119 assert mpl.rcParams[PARAM] == original_value 120 121 122def test_context_with_union_of_dict_and_namedstyle(): 123 # Test dict after style name where dict modifies the a different parameter. 124 original_value = 'gray' 125 other_param = 'text.usetex' 126 other_value = True 127 d = {other_param: other_value} 128 mpl.rcParams[PARAM] = original_value 129 mpl.rcParams[other_param] = (not other_value) 130 with temp_style('test', DUMMY_SETTINGS): 131 with style.context(['test', d]): 132 assert mpl.rcParams[PARAM] == VALUE 133 assert mpl.rcParams[other_param] == other_value 134 assert mpl.rcParams[PARAM] == original_value 135 assert mpl.rcParams[other_param] == (not other_value) 136 137 138def test_context_with_badparam(): 139 original_value = 'gray' 140 other_value = 'blue' 141 d = OrderedDict([(PARAM, original_value), ('badparam', None)]) 142 with style.context({PARAM: other_value}): 143 assert mpl.rcParams[PARAM] == other_value 144 x = style.context([d]) 145 with pytest.raises(KeyError): 146 with x: 147 pass 148 assert mpl.rcParams[PARAM] == other_value 149 150 151@pytest.mark.parametrize('equiv_styles', 152 [('mpl20', 'default'), 153 ('mpl15', 'classic')], 154 ids=['mpl20', 'mpl15']) 155def test_alias(equiv_styles): 156 rc_dicts = [] 157 for sty in equiv_styles: 158 with style.context(sty): 159 rc_dicts.append(mpl.rcParams.copy()) 160 161 rc_base = rc_dicts[0] 162 for nm, rc in zip(equiv_styles[1:], rc_dicts[1:]): 163 assert rc_base == rc 164 165 166def test_xkcd_no_cm(): 167 assert mpl.rcParams["path.sketch"] is None 168 plt.xkcd() 169 assert mpl.rcParams["path.sketch"] == (1, 100, 2) 170 gc.collect() 171 assert mpl.rcParams["path.sketch"] == (1, 100, 2) 172 173 174def test_xkcd_cm(): 175 assert mpl.rcParams["path.sketch"] is None 176 with plt.xkcd(): 177 assert mpl.rcParams["path.sketch"] == (1, 100, 2) 178 assert mpl.rcParams["path.sketch"] is None 179