test_sphinxext.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. """Tests for tinypages build using sphinx extensions."""
  2. import filecmp
  3. import os
  4. from pathlib import Path
  5. import shutil
  6. import sys
  7. from matplotlib.testing import subprocess_run_for_testing
  8. import pytest
  9. pytest.importorskip('sphinx',
  10. minversion=None if sys.version_info < (3, 10) else '4.1.3')
  11. def build_sphinx_html(source_dir, doctree_dir, html_dir, extra_args=None):
  12. # Build the pages with warnings turned into errors
  13. extra_args = [] if extra_args is None else extra_args
  14. cmd = [sys.executable, '-msphinx', '-W', '-b', 'html',
  15. '-d', str(doctree_dir), str(source_dir), str(html_dir), *extra_args]
  16. proc = subprocess_run_for_testing(
  17. cmd, capture_output=True, text=True,
  18. env={**os.environ, "MPLBACKEND": ""})
  19. out = proc.stdout
  20. err = proc.stderr
  21. assert proc.returncode == 0, \
  22. f"sphinx build failed with stdout:\n{out}\nstderr:\n{err}\n"
  23. if err:
  24. pytest.fail(f"sphinx build emitted the following warnings:\n{err}")
  25. assert html_dir.is_dir()
  26. def test_tinypages(tmp_path):
  27. shutil.copytree(Path(__file__).parent / 'tinypages', tmp_path,
  28. dirs_exist_ok=True)
  29. html_dir = tmp_path / '_build' / 'html'
  30. img_dir = html_dir / '_images'
  31. doctree_dir = tmp_path / 'doctrees'
  32. # Build the pages with warnings turned into errors
  33. cmd = [sys.executable, '-msphinx', '-W', '-b', 'html',
  34. '-d', str(doctree_dir),
  35. str(Path(__file__).parent / 'tinypages'), str(html_dir)]
  36. # On CI, gcov emits warnings (due to agg headers being included with the
  37. # same name in multiple extension modules -- but we don't care about their
  38. # coverage anyways); hide them using GCOV_ERROR_FILE.
  39. proc = subprocess_run_for_testing(
  40. cmd, capture_output=True, text=True,
  41. env={**os.environ, "MPLBACKEND": "", "GCOV_ERROR_FILE": os.devnull}
  42. )
  43. out = proc.stdout
  44. err = proc.stderr
  45. # Build the pages with warnings turned into errors
  46. build_sphinx_html(tmp_path, doctree_dir, html_dir)
  47. def plot_file(num):
  48. return img_dir / f'some_plots-{num}.png'
  49. def plot_directive_file(num):
  50. # This is always next to the doctree dir.
  51. return doctree_dir.parent / 'plot_directive' / f'some_plots-{num}.png'
  52. range_10, range_6, range_4 = [plot_file(i) for i in range(1, 4)]
  53. # Plot 5 is range(6) plot
  54. assert filecmp.cmp(range_6, plot_file(5))
  55. # Plot 7 is range(4) plot
  56. assert filecmp.cmp(range_4, plot_file(7))
  57. # Plot 11 is range(10) plot
  58. assert filecmp.cmp(range_10, plot_file(11))
  59. # Plot 12 uses the old range(10) figure and the new range(6) figure
  60. assert filecmp.cmp(range_10, plot_file('12_00'))
  61. assert filecmp.cmp(range_6, plot_file('12_01'))
  62. # Plot 13 shows close-figs in action
  63. assert filecmp.cmp(range_4, plot_file(13))
  64. # Plot 14 has included source
  65. html_contents = (html_dir / 'some_plots.html').read_bytes()
  66. assert b'# Only a comment' in html_contents
  67. # check plot defined in external file.
  68. assert filecmp.cmp(range_4, img_dir / 'range4.png')
  69. assert filecmp.cmp(range_6, img_dir / 'range6_range6.png')
  70. # check if figure caption made it into html file
  71. assert b'This is the caption for plot 15.' in html_contents
  72. # check if figure caption using :caption: made it into html file
  73. assert b'Plot 17 uses the caption option.' in html_contents
  74. # check if figure caption made it into html file
  75. assert b'This is the caption for plot 18.' in html_contents
  76. # check if the custom classes made it into the html file
  77. assert b'plot-directive my-class my-other-class' in html_contents
  78. # check that the multi-image caption is applied twice
  79. assert html_contents.count(b'This caption applies to both plots.') == 2
  80. # Plot 21 is range(6) plot via an include directive. But because some of
  81. # the previous plots are repeated, the argument to plot_file() is only 17.
  82. assert filecmp.cmp(range_6, plot_file(17))
  83. # plot 22 is from the range6.py file again, but a different function
  84. assert filecmp.cmp(range_10, img_dir / 'range6_range10.png')
  85. # Modify the included plot
  86. contents = (tmp_path / 'included_plot_21.rst').read_bytes()
  87. contents = contents.replace(b'plt.plot(range(6))', b'plt.plot(range(4))')
  88. (tmp_path / 'included_plot_21.rst').write_bytes(contents)
  89. # Build the pages again and check that the modified file was updated
  90. modification_times = [plot_directive_file(i).stat().st_mtime
  91. for i in (1, 2, 3, 5)]
  92. build_sphinx_html(tmp_path, doctree_dir, html_dir)
  93. assert filecmp.cmp(range_4, plot_file(17))
  94. # Check that the plots in the plot_directive folder weren't changed.
  95. # (plot_directive_file(1) won't be modified, but it will be copied to html/
  96. # upon compilation, so plot_file(1) will be modified)
  97. assert plot_directive_file(1).stat().st_mtime == modification_times[0]
  98. assert plot_directive_file(2).stat().st_mtime == modification_times[1]
  99. assert plot_directive_file(3).stat().st_mtime == modification_times[2]
  100. assert filecmp.cmp(range_10, plot_file(1))
  101. assert filecmp.cmp(range_6, plot_file(2))
  102. assert filecmp.cmp(range_4, plot_file(3))
  103. # Make sure that figures marked with context are re-created (but that the
  104. # contents are the same)
  105. assert plot_directive_file(5).stat().st_mtime > modification_times[3]
  106. assert filecmp.cmp(range_6, plot_file(5))
  107. def test_plot_html_show_source_link(tmp_path):
  108. parent = Path(__file__).parent
  109. shutil.copyfile(parent / 'tinypages/conf.py', tmp_path / 'conf.py')
  110. shutil.copytree(parent / 'tinypages/_static', tmp_path / '_static')
  111. doctree_dir = tmp_path / 'doctrees'
  112. (tmp_path / 'index.rst').write_text("""
  113. .. plot::
  114. plt.plot(range(2))
  115. """)
  116. # Make sure source scripts are created by default
  117. html_dir1 = tmp_path / '_build' / 'html1'
  118. build_sphinx_html(tmp_path, doctree_dir, html_dir1)
  119. assert len(list(html_dir1.glob("**/index-1.py"))) == 1
  120. # Make sure source scripts are NOT created when
  121. # plot_html_show_source_link` is False
  122. html_dir2 = tmp_path / '_build' / 'html2'
  123. build_sphinx_html(tmp_path, doctree_dir, html_dir2,
  124. extra_args=['-D', 'plot_html_show_source_link=0'])
  125. assert len(list(html_dir2.glob("**/index-1.py"))) == 0
  126. @pytest.mark.parametrize('plot_html_show_source_link', [0, 1])
  127. def test_show_source_link_true(tmp_path, plot_html_show_source_link):
  128. # Test that a source link is generated if :show-source-link: is true,
  129. # whether or not plot_html_show_source_link is true.
  130. parent = Path(__file__).parent
  131. shutil.copyfile(parent / 'tinypages/conf.py', tmp_path / 'conf.py')
  132. shutil.copytree(parent / 'tinypages/_static', tmp_path / '_static')
  133. doctree_dir = tmp_path / 'doctrees'
  134. (tmp_path / 'index.rst').write_text("""
  135. .. plot::
  136. :show-source-link: true
  137. plt.plot(range(2))
  138. """)
  139. html_dir = tmp_path / '_build' / 'html'
  140. build_sphinx_html(tmp_path, doctree_dir, html_dir, extra_args=[
  141. '-D', f'plot_html_show_source_link={plot_html_show_source_link}'])
  142. assert len(list(html_dir.glob("**/index-1.py"))) == 1
  143. @pytest.mark.parametrize('plot_html_show_source_link', [0, 1])
  144. def test_show_source_link_false(tmp_path, plot_html_show_source_link):
  145. # Test that a source link is NOT generated if :show-source-link: is false,
  146. # whether or not plot_html_show_source_link is true.
  147. parent = Path(__file__).parent
  148. shutil.copyfile(parent / 'tinypages/conf.py', tmp_path / 'conf.py')
  149. shutil.copytree(parent / 'tinypages/_static', tmp_path / '_static')
  150. doctree_dir = tmp_path / 'doctrees'
  151. (tmp_path / 'index.rst').write_text("""
  152. .. plot::
  153. :show-source-link: false
  154. plt.plot(range(2))
  155. """)
  156. html_dir = tmp_path / '_build' / 'html'
  157. build_sphinx_html(tmp_path, doctree_dir, html_dir, extra_args=[
  158. '-D', f'plot_html_show_source_link={plot_html_show_source_link}'])
  159. assert len(list(html_dir.glob("**/index-1.py"))) == 0
  160. def test_srcset_version(tmp_path):
  161. shutil.copytree(Path(__file__).parent / 'tinypages', tmp_path,
  162. dirs_exist_ok=True)
  163. html_dir = tmp_path / '_build' / 'html'
  164. img_dir = html_dir / '_images'
  165. doctree_dir = tmp_path / 'doctrees'
  166. build_sphinx_html(tmp_path, doctree_dir, html_dir, extra_args=[
  167. '-D', 'plot_srcset=2x'])
  168. def plot_file(num, suff=''):
  169. return img_dir / f'some_plots-{num}{suff}.png'
  170. # check some-plots
  171. for ind in [1, 2, 3, 5, 7, 11, 13, 15, 17]:
  172. assert plot_file(ind).exists()
  173. assert plot_file(ind, suff='.2x').exists()
  174. assert (img_dir / 'nestedpage-index-1.png').exists()
  175. assert (img_dir / 'nestedpage-index-1.2x.png').exists()
  176. assert (img_dir / 'nestedpage-index-2.png').exists()
  177. assert (img_dir / 'nestedpage-index-2.2x.png').exists()
  178. assert (img_dir / 'nestedpage2-index-1.png').exists()
  179. assert (img_dir / 'nestedpage2-index-1.2x.png').exists()
  180. assert (img_dir / 'nestedpage2-index-2.png').exists()
  181. assert (img_dir / 'nestedpage2-index-2.2x.png').exists()
  182. # Check html for srcset
  183. assert ('srcset="_images/some_plots-1.png, _images/some_plots-1.2x.png 2.00x"'
  184. in (html_dir / 'some_plots.html').read_text(encoding='utf-8'))
  185. st = ('srcset="../_images/nestedpage-index-1.png, '
  186. '../_images/nestedpage-index-1.2x.png 2.00x"')
  187. assert st in (html_dir / 'nestedpage/index.html').read_text(encoding='utf-8')
  188. st = ('srcset="../_images/nestedpage2-index-2.png, '
  189. '../_images/nestedpage2-index-2.2x.png 2.00x"')
  190. assert st in (html_dir / 'nestedpage2/index.html').read_text(encoding='utf-8')