Skip to content
  • Sponsor matplotlib/matplotlib

  • Notifications You must be signed in to change notification settings
  • Fork 7.9k

Dvi caching #10268

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

Closed
wants to merge 7 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions doc/api/next_api_changes/2018-02-16-JKS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
dviread changes
---------------

The ``format`` keyword argument to ``dviread.find_tex_file`` has been
deprecated. The function without the ``format`` argument, as well as
the new ``dviread.find_tex_files`` function, cache their results in
``texsupport.N.db`` in the cache directory to speed up dvi file
processing.
22 changes: 22 additions & 0 deletions doc/users/next_whats_new/texsupport_cache.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
TeX support cache
-----------------

The `usetex` feature sends snippets of TeX code to LaTeX and related
external tools for processing. This causes a nontrivial number of
helper processes to be spawned, which can be slow on some platforms.
A new cache database helps reduce the need to spawn these helper
processes, which should improve `usetex` processing speed.

The new cache files
~~~~~~~~~~~~~~~~~~~

The cache database is stored in a file named `texsupport.N.db` in the
standard cache directory (traditionally `$HOME/.matplotlib` but
possibly `$HOME/.cache/matplotlib`), where `N` stands for a version
number. The version number is incremented when new kinds of items are
added to the caching code, in order to avoid version clashes when
using multiple different versions of Matplotlib. The auxiliary files
`texsupport.N.db-wal` and `texsupport.N.db-shm` help coordinate usage
of the cache between concurrently running instances. All of these
cache files may be deleted when Matplotlib is not running, and
subsequent calls to the `usetex` code will recompute the TeX results.
10 changes: 5 additions & 5 deletions doc/users/whats_new.rst
Original file line number Diff line number Diff line change
@@ -14,12 +14,12 @@ revision, see the :ref:`github-stats`.
..
For a release, add a new section after this, then comment out the include
and toctree below by indenting them. Uncomment them after the release.
.. include:: next_whats_new/README.rst
.. toctree::
:glob:
:maxdepth: 1
.. include:: next_whats_new/README.rst
.. toctree::
:glob:
:maxdepth: 1

next_whats_new/*
next_whats_new/*


New in Matplotlib 2.2
1,040 changes: 892 additions & 148 deletions lib/matplotlib/dviread.py

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
23 changes: 23 additions & 0 deletions lib/matplotlib/tests/baseline_images/dviread/virtual.vpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
(FAMILY TESTING)
(COMMENT Test data for matplotlib)
(COMMENT Run vptovf virtual.vpl to obtain virtual.vf)
(FACE O 352)
(CODINGSCHEME TEX TEXT)
(DESIGNSIZE R 10.0)
(FONTDIMEN
(SLANT R 0.0)
(SPACE R 0.333334)
(STRETCH R 0.166667)
(SHRINK R 0.111112)
(XHEIGHT R 0.430555)
(QUAD R 1.000003)
(EXTRASPACE R 0.111112)
)
(MAPFONT D 0
(FONTNAME cmr10)
(FONTDSIZE R 10.0)
)
(MAPFONT D 1
(FONTNAME cmex10)
(FONTDSIZE R 10.0)
)
123 changes: 123 additions & 0 deletions lib/matplotlib/tests/test_dviread.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
from matplotlib.testing.decorators import skip_if_command_unavailable

try:
from unittest import mock
except ImportError:
import mock

import matplotlib.dviread as dr
import os.path
import json
import pytest
import sqlite3
import warnings


def test_PsfontsMap(monkeypatch):
@@ -68,3 +75,119 @@ def test_dviread():
'boxes': [[b.x, b.y, b.height, b.width] for b in page.boxes]}
for page in dvi]
assert data == correct


@skip_if_command_unavailable(["kpsewhich", "-version"])
def test_dviread_get_fonts():
dir = os.path.join(os.path.dirname(__file__), 'baseline_images', 'dviread')
dvi = dr._DviReader(os.path.join(dir, 'test.dvi'), None)
assert dvi.fontnames == \
{'cmex10', 'cmmi10', 'cmmi5', 'cmr10', 'cmr5', 'cmr7'}
vf = dr.Vf(os.path.join(dir, 'virtual.vf'))
assert vf.fontnames == {'cmex10', 'cmr10'}


def test_dviread_get_fonts_error_handling():
dir = os.path.join(os.path.dirname(__file__), 'baseline_images', 'dviread')
for n, message in [(1, "too few 223 bytes"),
(2, "post-postamble identification"),
(3, "postamble offset"),
(4, "postamble not found"),
(5, "opcode 127 in postamble")]:
with pytest.raises(ValueError) as e:
dr.Dvi(os.path.join(dir, "broken%d.dvi" % n), None)
assert message in str(e.value)


def test_TeXSupportCache(tmpdir):
dbfile = str(tmpdir / "test.db")
cache = dr.TeXSupportCache(filename=dbfile)
assert cache.get_pathnames(['foo', 'bar']) == {}
with cache.connection as transaction:
cache.update_pathnames({'foo': '/tmp/foo',
'xyzzy': '/xyzzy.dat',
'fontfile': None}, transaction)
assert cache.get_pathnames(['foo', 'bar']) == {'foo': '/tmp/foo'}
assert cache.get_pathnames(['xyzzy', 'fontfile']) == \
{'xyzzy': '/xyzzy.dat', 'fontfile': None}

# check that modifying a dvi file invalidates the cache
filename = str(tmpdir / "file.dvi")
with open(filename, "wb") as f:
f.write(b'qwerty')
os.utime(filename, (0, 0))
with cache.connection as t:
id1 = cache.dvi_new_file(filename, t)
assert cache.dvi_id(filename) == id1

with open(filename, "wb") as f:
f.write(b'asfdg')
os.utime(filename, (0, 0))
assert cache.dvi_id(filename) is None
with cache.connection as t:
id2 = cache.dvi_new_file(filename, t)
assert cache.dvi_id(filename) == id2


def test_TeXSupportCache_versioning(tmpdir):
dbfile = str(tmpdir / "test.db")
cache1 = dr.TeXSupportCache(dbfile)
with cache1.connection as transaction:
cache1.update_pathnames({'foo': '/tmp/foo'}, transaction)

with sqlite3.connect(dbfile, isolation_level="DEFERRED") as conn:
conn.executescript('PRAGMA user_version=1000000000;')

with pytest.raises(dr.TeXSupportCacheError):
cache2 = dr.TeXSupportCache(dbfile)


def test_find_tex_files(tmpdir):
with mock.patch('matplotlib.dviread.subprocess.Popen') as mock_popen:
mock_proc = mock.Mock()
stdout = '{s}tmp{s}foo.pfb\n{s}tmp{s}bar.map\n'.\
format(s=os.path.sep).encode('ascii')
mock_proc.configure_mock(**{'communicate.return_value': (stdout, b'')})
mock_popen.return_value = mock_proc

# first call uses the results from kpsewhich
cache = dr.TeXSupportCache(filename=str(tmpdir / "test.db"))
assert dr.find_tex_files(
['foo.pfb', 'cmsy10.pfb', 'bar.tmp', 'bar.map'], cache) \
== {'foo.pfb': '{s}tmp{s}foo.pfb'.format(s=os.path.sep),
'bar.map': '{s}tmp{s}bar.map'.format(s=os.path.sep),
'cmsy10.pfb': None, 'bar.tmp': None}
assert mock_popen.called

# second call (subset of the first one) uses only the cache
mock_popen.reset_mock()
assert dr.find_tex_files(['foo.pfb', 'cmsy10.pfb'], cache) \
== {'foo.pfb': '{s}tmp{s}foo.pfb'.format(s=os.path.sep),
'cmsy10.pfb': None}
assert not mock_popen.called

# third call (includes more than the first one) uses kpsewhich again
mock_popen.reset_mock()
stdout = '{s}usr{s}local{s}cmr10.tfm\n'.\
format(s=os.path.sep).encode('ascii')
mock_proc.configure_mock(**{'communicate.return_value': (stdout, b'')})
mock_popen.return_value = mock_proc
assert dr.find_tex_files(['foo.pfb', 'cmr10.tfm'], cache) == \
{'foo.pfb': '{s}tmp{s}foo.pfb'.format(s=os.path.sep),
'cmr10.tfm': '{s}usr{s}local{s}cmr10.tfm'.format(s=os.path.sep)}
assert mock_popen.called


def test_find_tex_file_format():
with mock.patch('matplotlib.dviread.subprocess.Popen') as mock_popen:
mock_proc = mock.Mock()
stdout = b'/foo/bar/baz\n'
mock_proc.configure_mock(**{'communicate.return_value': (stdout, b'')})
mock_popen.return_value = mock_proc

warnings.filterwarnings(
'ignore',
'The format option to find_tex_file is deprecated.*',
UserWarning)
assert dr.find_tex_file('foobar', format='tfm') == '/foo/bar/baz'
assert mock_popen.called