You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			219 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			219 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Python
		
	
"""
 | 
						|
Test output reproducibility.
 | 
						|
"""
 | 
						|
 | 
						|
import os
 | 
						|
import sys
 | 
						|
 | 
						|
import pytest
 | 
						|
 | 
						|
import matplotlib as mpl
 | 
						|
from matplotlib import pyplot as plt
 | 
						|
from matplotlib.cbook import get_sample_data
 | 
						|
from matplotlib.collections import PathCollection
 | 
						|
from matplotlib.image import BboxImage
 | 
						|
from matplotlib.offsetbox import AnchoredOffsetbox, AuxTransformBox
 | 
						|
from matplotlib.patches import Circle, PathPatch
 | 
						|
from matplotlib.path import Path
 | 
						|
from matplotlib.testing import subprocess_run_for_testing
 | 
						|
from matplotlib.testing._markers import needs_ghostscript, needs_usetex
 | 
						|
import matplotlib.testing.compare
 | 
						|
from matplotlib.text import TextPath
 | 
						|
from matplotlib.transforms import IdentityTransform
 | 
						|
 | 
						|
 | 
						|
def _save_figure(objects='mhip', 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')
 | 
						|
 | 
						|
    if 'p' in objects:
 | 
						|
 | 
						|
        # clipping support class, copied from demo_text_path.py gallery example
 | 
						|
        class PathClippedImagePatch(PathPatch):
 | 
						|
            """
 | 
						|
            The given image is used to draw the face of the patch. Internally,
 | 
						|
            it uses BboxImage whose clippath set to the path of the patch.
 | 
						|
 | 
						|
            FIXME : The result is currently dpi dependent.
 | 
						|
            """
 | 
						|
 | 
						|
            def __init__(self, path, bbox_image, **kwargs):
 | 
						|
                super().__init__(path, **kwargs)
 | 
						|
                self.bbox_image = BboxImage(
 | 
						|
                    self.get_window_extent, norm=None, origin=None)
 | 
						|
                self.bbox_image.set_data(bbox_image)
 | 
						|
 | 
						|
            def set_facecolor(self, color):
 | 
						|
                """Simply ignore facecolor."""
 | 
						|
                super().set_facecolor("none")
 | 
						|
 | 
						|
            def draw(self, renderer=None):
 | 
						|
                # the clip path must be updated every draw. any solution? -JJ
 | 
						|
                self.bbox_image.set_clip_path(self._path, self.get_transform())
 | 
						|
                self.bbox_image.draw(renderer)
 | 
						|
                super().draw(renderer)
 | 
						|
 | 
						|
        # add a polar projection
 | 
						|
        px = fig.add_subplot(projection="polar")
 | 
						|
        pimg = px.imshow([[2]])
 | 
						|
        pimg.set_clip_path(Circle((0, 1), radius=0.3333))
 | 
						|
 | 
						|
        # add a text-based clipping path (origin: demo_text_path.py)
 | 
						|
        (ax1, ax2) = fig.subplots(2)
 | 
						|
        arr = plt.imread(get_sample_data("grace_hopper.jpg"))
 | 
						|
        text_path = TextPath((0, 0), "!?", size=150)
 | 
						|
        p = PathClippedImagePatch(text_path, arr, ec="k")
 | 
						|
        offsetbox = AuxTransformBox(IdentityTransform())
 | 
						|
        offsetbox.add_artist(p)
 | 
						|
        ao = AnchoredOffsetbox(loc='upper left', child=offsetbox, frameon=True,
 | 
						|
                               borderpad=0.2)
 | 
						|
        ax1.add_artist(ao)
 | 
						|
 | 
						|
        # add a 2x2 grid of path-clipped axes (origin: test_artist.py)
 | 
						|
        exterior = Path.unit_rectangle().deepcopy()
 | 
						|
        exterior.vertices *= 4
 | 
						|
        exterior.vertices -= 2
 | 
						|
        interior = Path.unit_circle().deepcopy()
 | 
						|
        interior.vertices = interior.vertices[::-1]
 | 
						|
        clip_path = Path.make_compound_path(exterior, interior)
 | 
						|
 | 
						|
        star = Path.unit_regular_star(6).deepcopy()
 | 
						|
        star.vertices *= 2.6
 | 
						|
 | 
						|
        (row1, row2) = fig.subplots(2, 2, sharex=True, sharey=True)
 | 
						|
        for row in (row1, row2):
 | 
						|
            ax1, ax2 = row
 | 
						|
            collection = PathCollection([star], lw=5, edgecolor='blue',
 | 
						|
                                        facecolor='red', alpha=0.7, hatch='*')
 | 
						|
            collection.set_clip_path(clip_path, ax1.transData)
 | 
						|
            ax1.add_collection(collection)
 | 
						|
 | 
						|
            patch = PathPatch(star, lw=5, edgecolor='blue', facecolor='red',
 | 
						|
                              alpha=0.7, hatch='*')
 | 
						|
            patch.set_clip_path(clip_path, ax2.transData)
 | 
						|
            ax2.add_patch(patch)
 | 
						|
 | 
						|
            ax1.set_xlim([-3, 3])
 | 
						|
            ax1.set_ylim([-3, 3])
 | 
						|
 | 
						|
    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),
 | 
						|
        ("mhip", "pdf", False),
 | 
						|
        ("mhip", "ps", False),
 | 
						|
        pytest.param(
 | 
						|
            "mhip", "ps", True, marks=[needs_usetex, needs_ghostscript]),
 | 
						|
        ("p", "svg", False),
 | 
						|
        ("mhip", "svg", False),
 | 
						|
        pytest.param("mhip", "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, and 'p' for paths.
 | 
						|
    fmt : {"pdf", "ps", "svg"}
 | 
						|
        Output format.
 | 
						|
    """
 | 
						|
    plots = [
 | 
						|
        subprocess_run_for_testing(
 | 
						|
            [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",
 | 
						|
                 "MPLBACKEND": "Agg"},
 | 
						|
            text=False, capture_output=True, check=True).stdout
 | 
						|
        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_run_for_testing(
 | 
						|
        [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",
 | 
						|
             "MPLBACKEND": "Agg"}, capture_output=True, text=False, check=True).stdout
 | 
						|
    assert string in buf
 |