diff --git a/doc/api/next_api_changes/2019-07-23-AL.rst b/doc/api/next_api_changes/2019-07-23-AL.rst new file mode 100644 index 000000000000..07d6db9f9815 --- /dev/null +++ b/doc/api/next_api_changes/2019-07-23-AL.rst @@ -0,0 +1,5 @@ +API changes +``````````` + +The ``matplotlib.testing.determinism`` module, which exposes no public API, has +been deleted. diff --git a/lib/matplotlib/testing/determinism.py b/lib/matplotlib/testing/determinism.py deleted file mode 100644 index 06b7dee52ce6..000000000000 --- a/lib/matplotlib/testing/determinism.py +++ /dev/null @@ -1,134 +0,0 @@ -""" -Provides utilities to test output reproducibility. -""" - -import os -import re -import subprocess -import sys - -import pytest - -import matplotlib -from matplotlib import pyplot as plt - - -def _determinism_save(objects='mhi', format="pdf", usetex=False): - # save current value of SOURCE_DATE_EPOCH and set it - # to a constant value, so that time difference is not - # taken into account - sde = os.environ.pop('SOURCE_DATE_EPOCH', None) - os.environ['SOURCE_DATE_EPOCH'] = "946684800" - - matplotlib.rcParams['text.usetex'] = usetex - - fig = plt.figure() - - if 'm' in objects: - # use different markers... - ax1 = fig.add_subplot(1, 6, 1) - x = range(10) - ax1.plot(x, [1] * 10, marker='D') - ax1.plot(x, [2] * 10, marker='x') - ax1.plot(x, [3] * 10, marker='^') - ax1.plot(x, [4] * 10, marker='H') - ax1.plot(x, [5] * 10, marker='v') - - if 'h' in objects: - # also use different hatch patterns - ax2 = fig.add_subplot(1, 6, 2) - bars = (ax2.bar(range(1, 5), range(1, 5)) + - ax2.bar(range(1, 5), [6] * 4, bottom=range(1, 5))) - ax2.set_xticks([1.5, 2.5, 3.5, 4.5]) - - patterns = ('-', '+', 'x', '\\', '*', 'o', 'O', '.') - for bar, pattern in zip(bars, patterns): - bar.set_hatch(pattern) - - if 'i' in objects: - # also use different images - A = [[1, 2, 3], [2, 3, 1], [3, 1, 2]] - fig.add_subplot(1, 6, 3).imshow(A, interpolation='nearest') - A = [[1, 3, 2], [1, 2, 3], [3, 1, 2]] - fig.add_subplot(1, 6, 4).imshow(A, interpolation='bilinear') - A = [[2, 3, 1], [1, 2, 3], [2, 1, 3]] - fig.add_subplot(1, 6, 5).imshow(A, interpolation='bicubic') - - x = range(5) - fig.add_subplot(1, 6, 6).plot(x, x) - - stdout = getattr(sys.stdout, 'buffer', sys.stdout) - fig.savefig(stdout, format=format) - - # Restores SOURCE_DATE_EPOCH - if sde is None: - os.environ.pop('SOURCE_DATE_EPOCH', None) - else: - os.environ['SOURCE_DATE_EPOCH'] = sde - - -def _determinism_check(objects='mhi', format="pdf", usetex=False): - """ - Output three times the same graphs and checks that the outputs are exactly - the same. - - Parameters - ---------- - objects : str - contains characters corresponding to objects to be included in the test - document: 'm' for markers, 'h' for hatch patterns, 'i' for images. The - default value is "mhi", so that the test includes all these objects. - format : str - format string. The default value is "pdf". - """ - plots = [] - for i in range(3): - result = subprocess.check_output([ - sys.executable, '-R', '-c', - 'import matplotlib; ' - 'matplotlib._called_from_pytest = True; ' - 'matplotlib.use(%r); ' - 'from matplotlib.testing.determinism import _determinism_save;' - '_determinism_save(%r, %r, %r)' - % (format, objects, format, usetex)]) - plots.append(result) - for p in plots[1:]: - if usetex: - if p != plots[0]: - pytest.skip("failed, maybe due to ghostscript timestamps") - else: - assert p == plots[0] - - -def _determinism_source_date_epoch(format, string, keyword=b"CreationDate"): - """ - Test SOURCE_DATE_EPOCH support. Output a document with the environment - variable SOURCE_DATE_EPOCH set to 2000-01-01 00:00 UTC and check that the - document contains the timestamp that corresponds to this date (given as an - argument). - - Parameters - ---------- - format : str - format string, such as "pdf". - string : str - timestamp string for 2000-01-01 00:00 UTC. - keyword : bytes - a string to look at when searching for the timestamp in the document - (used in case the test fails). - """ - buff = subprocess.check_output([ - sys.executable, '-R', '-c', - 'import matplotlib; ' - 'matplotlib._called_from_pytest = True; ' - 'matplotlib.use(%r); ' - 'from matplotlib.testing.determinism import _determinism_save;' - '_determinism_save(%r, %r)' - % (format, "", format)]) - find_keyword = re.compile(b".*" + keyword + b".*") - key = find_keyword.search(buff) - if key: - print(key.group()) - else: - print("Timestamp keyword (%s) not found!" % keyword) - assert string in buff diff --git a/lib/matplotlib/tests/test_backend_pdf.py b/lib/matplotlib/tests/test_backend_pdf.py index 122e1e5f8a3b..d6989066b264 100644 --- a/lib/matplotlib/tests/test_backend_pdf.py +++ b/lib/matplotlib/tests/test_backend_pdf.py @@ -12,8 +12,6 @@ from matplotlib.backends.backend_pdf import PdfPages from matplotlib.testing.compare import compare_images from matplotlib.testing.decorators import image_comparison -from matplotlib.testing.determinism import (_determinism_source_date_epoch, - _determinism_check) with warnings.catch_warnings(): @@ -137,36 +135,6 @@ def test_pdfpages_fspath(): pdf.savefig(plt.figure()) -def test_source_date_epoch(): - """Test SOURCE_DATE_EPOCH support for PDF output""" - _determinism_source_date_epoch("pdf", b"/CreationDate (D:20000101000000Z)") - - -def test_determinism_plain(): - """Test for reproducible PDF output: simple figure""" - _determinism_check('', format="pdf") - - -def test_determinism_images(): - """Test for reproducible PDF output: figure with different images""" - _determinism_check('i', format="pdf") - - -def test_determinism_hatches(): - """Test for reproducible PDF output: figure with different hatches""" - _determinism_check('h', format="pdf") - - -def test_determinism_markers(): - """Test for reproducible PDF output: figure with different markers""" - _determinism_check('m', format="pdf") - - -def test_determinism_all(): - """Test for reproducible PDF output""" - _determinism_check(format="pdf") - - @image_comparison(['hatching_legend.pdf']) def test_hatching_legend(): """Test for correct hatching on patches in legend""" diff --git a/lib/matplotlib/tests/test_backend_ps.py b/lib/matplotlib/tests/test_backend_ps.py index 32daee439a41..9bcf10828878 100644 --- a/lib/matplotlib/tests/test_backend_ps.py +++ b/lib/matplotlib/tests/test_backend_ps.py @@ -11,8 +11,6 @@ import matplotlib.pyplot as plt from matplotlib import cbook, patheffects from matplotlib.testing.decorators import image_comparison -from matplotlib.testing.determinism import (_determinism_source_date_epoch, - _determinism_check) with warnings.catch_warnings(): @@ -98,28 +96,6 @@ def test_tilde_in_tempfilename(tmpdir): plt.savefig(base_tempdir / 'tex_demo.eps', format="ps") -def test_source_date_epoch(): - """Test SOURCE_DATE_EPOCH support for PS output""" - # SOURCE_DATE_EPOCH support is not tested with text.usetex, - # because the produced timestamp comes from ghostscript: - # %%CreationDate: D:20000101000000Z00\'00\', and this could change - # with another ghostscript version. - _determinism_source_date_epoch( - "ps", b"%%CreationDate: Sat Jan 01 00:00:00 2000") - - -def test_determinism_all(): - """Test for reproducible PS output""" - _determinism_check(format="ps") - - -@needs_usetex -@needs_ghostscript -def test_determinism_all_tex(): - """Test for reproducible PS/tex output""" - _determinism_check(format="ps", usetex=True) - - @image_comparison(["empty.eps"]) def test_transparency(): fig, ax = plt.subplots() diff --git a/lib/matplotlib/tests/test_backend_svg.py b/lib/matplotlib/tests/test_backend_svg.py index ca3be9eee666..46d8d655dfd3 100644 --- a/lib/matplotlib/tests/test_backend_svg.py +++ b/lib/matplotlib/tests/test_backend_svg.py @@ -98,65 +98,6 @@ def test_bold_font_output_with_none_fonttype(): ax.set_title('bold-title', fontweight='bold') -def _test_determinism_save(filename, usetex): - # This function is mostly copy&paste from "def test_visibility" - mpl.rc('svg', hashsalt='asdf') - mpl.rc('text', usetex=usetex) - - fig = Figure() # Require no GUI. - ax = fig.add_subplot(111) - - x = np.linspace(0, 4 * np.pi, 50) - y = np.sin(x) - yerr = np.ones_like(y) - - a, b, c = ax.errorbar(x, y, yerr=yerr, fmt='ko') - for artist in b: - artist.set_visible(False) - ax.set_title('A string $1+2+\\sigma$') - ax.set_xlabel('A string $1+2+\\sigma$') - ax.set_ylabel('A string $1+2+\\sigma$') - - fig.savefig(filename, format="svg") - - -@pytest.mark.parametrize( - "filename, usetex", - # unique filenames to allow for parallel testing - [("determinism_notex.svg", False), - pytest.param("determinism_tex.svg", True, marks=needs_usetex)]) -def test_determinism(filename, usetex): - import sys - from subprocess import check_output, STDOUT, CalledProcessError - plots = [] - for i in range(3): - # Using check_output and setting stderr to STDOUT will capture the real - # problem in the output property of the exception - try: - check_output( - [sys.executable, '-R', '-c', - 'import matplotlib; ' - 'matplotlib._called_from_pytest = True; ' - 'matplotlib.use("svg", force=True); ' - 'from matplotlib.tests.test_backend_svg ' - 'import _test_determinism_save;' - '_test_determinism_save(%r, %r)' % (filename, usetex)], - stderr=STDOUT) - except CalledProcessError as e: - # it's easier to use utf8 and ask for forgiveness than try - # to figure out what the current console has as an - # encoding :-/ - print(e.output.decode(encoding="utf-8", errors="ignore")) - raise e - else: - with open(filename, 'rb') as fd: - plots.append(fd.read()) - finally: - os.unlink(filename) - for p in plots[1:]: - assert p == plots[0] - - @needs_usetex def test_missing_psfont(monkeypatch): """An error is raised if a TeX font lacks a Type-1 equivalent""" diff --git a/lib/matplotlib/tests/test_determinism.py b/lib/matplotlib/tests/test_determinism.py new file mode 100644 index 000000000000..640c7450cea8 --- /dev/null +++ b/lib/matplotlib/tests/test_determinism.py @@ -0,0 +1,143 @@ +""" +Test output reproducibility. +""" + +import os +import subprocess +import sys + +import pytest + +import matplotlib as mpl +import matplotlib.testing.compare +from matplotlib import pyplot as plt + + +needs_ghostscript = pytest.mark.skipif( + "eps" not in mpl.testing.compare.converter, + reason="This test needs a ghostscript installation") +needs_usetex = pytest.mark.skipif( + not mpl.checkdep_usetex(True), + reason="This test needs a TeX installation") + + +def _save_figure(objects='mhi', fmt="pdf", usetex=False): + mpl.use(fmt) + mpl.rcParams.update({'svg.hashsalt': 'asdf', 'text.usetex': usetex}) + + fig = plt.figure() + + if 'm' in objects: + # use different markers... + ax1 = fig.add_subplot(1, 6, 1) + x = range(10) + ax1.plot(x, [1] * 10, marker='D') + ax1.plot(x, [2] * 10, marker='x') + ax1.plot(x, [3] * 10, marker='^') + ax1.plot(x, [4] * 10, marker='H') + ax1.plot(x, [5] * 10, marker='v') + + if 'h' in objects: + # also use different hatch patterns + ax2 = fig.add_subplot(1, 6, 2) + bars = (ax2.bar(range(1, 5), range(1, 5)) + + ax2.bar(range(1, 5), [6] * 4, bottom=range(1, 5))) + ax2.set_xticks([1.5, 2.5, 3.5, 4.5]) + + patterns = ('-', '+', 'x', '\\', '*', 'o', 'O', '.') + for bar, pattern in zip(bars, patterns): + bar.set_hatch(pattern) + + if 'i' in objects: + # also use different images + A = [[1, 2, 3], [2, 3, 1], [3, 1, 2]] + fig.add_subplot(1, 6, 3).imshow(A, interpolation='nearest') + A = [[1, 3, 2], [1, 2, 3], [3, 1, 2]] + fig.add_subplot(1, 6, 4).imshow(A, interpolation='bilinear') + A = [[2, 3, 1], [1, 2, 3], [2, 1, 3]] + fig.add_subplot(1, 6, 5).imshow(A, interpolation='bicubic') + + x = range(5) + ax = fig.add_subplot(1, 6, 6) + ax.plot(x, x) + ax.set_title('A string $1+2+\\sigma$') + ax.set_xlabel('A string $1+2+\\sigma$') + ax.set_ylabel('A string $1+2+\\sigma$') + + stdout = getattr(sys.stdout, 'buffer', sys.stdout) + fig.savefig(stdout, format=fmt) + + +@pytest.mark.parametrize( + "objects, fmt, usetex", [ + ("", "pdf", False), + ("m", "pdf", False), + ("h", "pdf", False), + ("i", "pdf", False), + ("mhi", "pdf", False), + ("mhi", "ps", False), + pytest.param( + "mhi", "ps", True, marks=[needs_usetex, needs_ghostscript]), + ("mhi", "svg", False), + pytest.param("mhi", "svg", True, marks=needs_usetex), + ] +) +def test_determinism_check(objects, fmt, usetex): + """ + Output three times the same graphs and checks that the outputs are exactly + the same. + + Parameters + ---------- + objects : str + Objects to be included in the test document: 'm' for markers, 'h' for + hatch patterns, 'i' for images. + fmt : {"pdf", "ps", "svg"} + Output format. + """ + plots = [ + subprocess.check_output( + [sys.executable, "-R", "-c", + f"from matplotlib.tests.test_determinism import _save_figure;" + f"_save_figure({objects!r}, {fmt!r}, {usetex})"], + env={**os.environ, "SOURCE_DATE_EPOCH": "946684800"}) + for _ in range(3) + ] + for p in plots[1:]: + if fmt == "ps" and usetex: + if p != plots[0]: + pytest.skip("failed, maybe due to ghostscript timestamps") + else: + assert p == plots[0] + + +@pytest.mark.parametrize( + "fmt, string", [ + ("pdf", b"/CreationDate (D:20000101000000Z)"), + # SOURCE_DATE_EPOCH support is not tested with text.usetex, + # because the produced timestamp comes from ghostscript: + # %%CreationDate: D:20000101000000Z00\'00\', and this could change + # with another ghostscript version. + ("ps", b"%%CreationDate: Sat Jan 01 00:00:00 2000"), + ] +) +def test_determinism_source_date_epoch(fmt, string): + """ + Test SOURCE_DATE_EPOCH support. Output a document with the environment + variable SOURCE_DATE_EPOCH set to 2000-01-01 00:00 UTC and check that the + document contains the timestamp that corresponds to this date (given as an + argument). + + Parameters + ---------- + fmt : {"pdf", "ps", "svg"} + Output format. + string : bytes + Timestamp string for 2000-01-01 00:00 UTC. + """ + buf = subprocess.check_output( + [sys.executable, "-R", "-c", + f"from matplotlib.tests.test_determinism import _save_figure; " + f"_save_figure('', {fmt!r})"], + env={**os.environ, "SOURCE_DATE_EPOCH": "946684800"}) + assert string in buf