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

"""
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