-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Reproducible PS/PDF output (master) #6597
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
c6a4660
To allow reproducible output:
4e477dc
Reproducible PDF output: sort TTF characters
946ae45
Reproducible PDF output: sort fonts
22f71a2
Tests for determinist PDF output:
ad660d7
Share determinism test code for PS and PDF output.
37c28b4
Reproducible PS/tex output.
2e1c773
Removes test_source_date_epoch_tex test, since this is ghostscript de…
541f97e
PEP8
2a6ebc8
Add what's new section
6ee8967
Add some insight when test_source_date_epoch fails.
a3185e6
Allow parallel execution of test_backend_ps:test_determinism_all_tex …
8f095f6
Use subprocess for _test_source_date_epoch, to allow parallel calls (…
c007f49
Change SOURCE_DATE_EPOCH test date, to use two-digits numbers for mon…
ecbdd55
PEP8
65ec88e
Warnings about possible unreproducibility issues
5b405cc
Doc rephrasing, thanks to jkseppan.
d10a21e
Use explicit date formatting for PS backend timestamp, instead of asc…
da55bb6
Revert to 2000-01-01 for the SOURCE_DATE_EPOCH test date.
c56dae7
Use standard date format for PS timestamp
995173d
Rename functions in determinism.py to remove `test' keyword, since th…
eef6b12
TST: Use standard I/O for determinism tests.
QuLogic fb529da
TST: Remove multiple nested imports.
QuLogic ebff832
TST: Fix compatibility with Python 2.
QuLogic f6301c2
Merge pull request #1 from QuLogic/reproducible-master
1786555
Adds __future__ imports to testing/determinism.py
af4213e
Pass usetex setting to _determinism_save
bf7387e
Skip test using ghostscript, since failing may be due to ghostscript …
2cdc577
Forgot to change one timestamp format in c56dae7c52af50ceaca33ba14717…
76bec02
Reuse UTC timezone from dates.py
bbab0c5
Removes now useless option uid for _determinism_check
1a5ada6
Typo
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
Reproducible PS and PDF output | ||
------------------------------ | ||
|
||
The ``SOURCE_DATE_EPOCH`` environment variable can now be used to set | ||
the timestamp value in the PS and PDF outputs. See | ||
https://reproducible-builds.org/specs/source-date-epoch/ | ||
|
||
The reproducibility of the output from the PS and PDF backends has so | ||
far been tested using various plot elements but only default values of | ||
options such as ``{ps,pdf}.fonttype`` that can affect the output at a | ||
low level, and not with the mathtext or usetex features. When | ||
matplotlib calls external tools (such as PS distillers or LaTeX) their | ||
versions need to be kept constant for reproducibility, and they may | ||
add sources of nondeterminism outside the control of matplotlib. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
""" | ||
Provides utilities to test output reproducibility. | ||
""" | ||
|
||
from __future__ import (absolute_import, division, print_function, | ||
unicode_literals) | ||
|
||
import six | ||
|
||
import io | ||
import os | ||
import re | ||
import sys | ||
from subprocess import check_output | ||
|
||
import matplotlib | ||
from matplotlib import pyplot as plt | ||
|
||
from nose.plugins.skip import SkipTest | ||
|
||
|
||
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=u'D') | ||
ax1.plot(x, [2] * 10, marker=u'x') | ||
ax1.plot(x, [3] * 10, marker=u'^') | ||
ax1.plot(x, [4] * 10, marker=u'H') | ||
ax1.plot(x, [5] * 10, marker=u'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) | ||
|
||
if six.PY2 and format == 'ps': | ||
stdout = io.StringIO() | ||
else: | ||
stdout = getattr(sys.stdout, 'buffer', sys.stdout) | ||
fig.savefig(stdout, format=format) | ||
if six.PY2 and format == 'ps': | ||
sys.stdout.write(stdout.getvalue()) | ||
|
||
# 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". | ||
""" | ||
from nose.tools import assert_equal | ||
plots = [] | ||
for i in range(3): | ||
result = check_output([sys.executable, '-R', '-c', | ||
'import matplotlib; ' | ||
'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]: | ||
raise SkipTest("failed, maybe due to ghostscript timestamps") | ||
else: | ||
assert_equal(p, plots[0]) | ||
|
||
|
||
def _determinism_source_date_epoch(format, string, keyword=b"CreationDate"): | ||
""" | ||
Test SOURCE_DATE_EPOCH support. Output a document with the envionment | ||
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 = check_output([sys.executable, '-R', '-c', | ||
'import matplotlib; ' | ||
'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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just
fig.savefig(getattr(sys.stdout, 'buffer', sys.stdout), format=format)
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does not work with the PS backend, which tries to wrap the input in
TextIOWrapper
.