diff options
Diffstat (limited to 'paper/lua-filters/minted/run_minted_tests.py')
-rwxr-xr-x | paper/lua-filters/minted/run_minted_tests.py | 522 |
1 files changed, 522 insertions, 0 deletions
diff --git a/paper/lua-filters/minted/run_minted_tests.py b/paper/lua-filters/minted/run_minted_tests.py new file mode 100755 index 0000000..15803da --- /dev/null +++ b/paper/lua-filters/minted/run_minted_tests.py @@ -0,0 +1,522 @@ +#!/usr/bin/env python + +""" +Unit tests for the pandoc minted.lua filter. +""" + +# Lint this file with: flake8 --max-line-length=80 +import os +import string +import subprocess +import sys +import textwrap + +code_block = textwrap.dedent(''' + ## A Code Block + + ```{.cpp} + auto mult = []<typename T, typename U>(T const & x, U const & y) { + return x * y; + }; + ``` +''') +""" +The base CodeBlock code. {.cpp} is used as a replacement marker in most tests! +""" + +inline_delims = '|!@#^&*-=+' + string.digits + string.ascii_letters +inline_code = textwrap.dedent(''' + ## Inline Code + + `#include <type_traits>`{.cpp} + C and C++ use `{` and `}` to delimit scopes. + Some other special characters: + These check bypass: `~!@#$%^&*()-=_+[]\\{}|;\':",./<>?` + These check regular inline: ''' + ' '.join( + '`{' + inline_delims[:i] + '`' for i in range(len(inline_delims)) +)) +""" +The base Code code. {.cpp} is used as a replacement marker in most tests! +""" + + +def run_pandoc(pandoc_args, stdin): + """Run pandoc with the specified arguments, returning the output.""" + # The input / output should be small enough for these tests that buffer + # overflows should not happen. + pandoc_proc = subprocess.Popen( + ["pandoc"] + pandoc_args, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE + ) + + # Python 3.x and later require communicating with bytes. + if sys.version_info[0] >= 3: + stdin = bytes(stdin, "utf-8") + + stdout, stderr = pandoc_proc.communicate(input=stdin) + if pandoc_proc.returncode != 0: + sys.stderr.write("Non-zero exit code of {ret} from pandoc!\n".format( + ret=pandoc_proc.returncode + )) + sys.stderr.write("pandoc stderr: {stderr}".format( + stderr=stderr.decode("utf-8") + )) + sys.exit(1) + + return stdout.decode("utf-8") + + +def fail_test(test_name, messages, ansi_color_code="31"): + """ + Print failure message and ``sys.exit(1)``. + + ``test_name`` (str) + The name of the test (to make finding in code easier). + + ``messages`` (list of str -- or -- str) + A single string, or list of strings, to print out to ``stderr`` that + explain the reason for the test failure. + + ``ansi_color_code`` (str) + A an ANSI color code to use to colorize the failure message :) Default + is ``"31"``, which is red. + """ + sys.stderr.write( + "\033[0;{ansi_color_code}mTest {test_name} FAILED\033[0m\n".format( + ansi_color_code=ansi_color_code, test_name=test_name + ) + ) + if isinstance(messages, list): + for m in messages: + sys.stderr.write("--> {m}\n".format(m=m)) + else: + sys.stderr.write("--> {messages}\n".format(messages=messages)) + sys.exit(1) + + +def ensure_fragile(test_name, pandoc_output): + r""" + Ensure that every \begin{frame} has (at least one) fragile. + + ``test_name`` (str) + The name of the test (forwards to ``fail_test``). + + ``pandoc_output`` (str) + The pandoc output for the test case. + """ + for line in pandoc_output.splitlines(): + if r"\begin{frame}" in line: + if "fragile" not in line: + fail_test( + test_name, + r"\begin{frame} without 'fragile': {line}".format(line=line) + ) + + +def ensure_present(test_name, string, pandoc_output): + """ + Assert that ``string`` is found in ``pandoc_output``. + + ``test_name`` (str) + The name of the test (forwards to ``fail_test``). + + ``string`` (str) + The string to check verbatim ``string in pandoc_output``. + + ``pandoc_output`` (str) + The pandoc output for the test case. + """ + if string not in pandoc_output: + fail_test( + test_name, + "The requested string '{string}' was not found in:\n{pout}".format( + string=string, pout=pandoc_output + ) + ) + + +def ensure_not_present(test_name, string, pandoc_output): + """ + Assert that ``string`` is **not** found in ``pandoc_output``. + + ``test_name`` (str) + The name of the test (forwards to ``fail_test``). + + ``string`` (str) + The string to check verbatim ``string not in pandoc_output``. + + ``pandoc_output`` (str) + The pandoc output for the test case. + """ + if string in pandoc_output: + fail_test( + test_name, + "The forbidden string '{string}' was found in:\n{pout}".format( + string=string, pout=pandoc_output + ) + ) + + +def run_tex_tests(pandoc_args, fmt): + """ + Run same tests for latex writers. + + ``pandoc_args`` (list of str) + The base list of arguments to forward to pandoc. Some tests may remove + the ``--no-highlight`` flag to validate whether or not pandoc + highlighting macros appear as expected (or not at all). + + ``fmt`` (str) + The format is assumed to be either 'latex' or 'beamer'. + """ + def verify(test_name, args, md, *strings): + """Run pandoc, ensure fragile, and string in output.""" + output = run_pandoc(args + ["-t", fmt], md) + if fmt == "beamer": + ensure_fragile(test_name, output) + else: # latex writer + ensure_not_present(test_name, "fragile", output) + for s in strings: + ensure_present(test_name, s, output) + # Make sure the pandoc highlighting is not being used + if "--no-highlight" in args: + ensure_not_present(test_name, r"\VERB", output) + # if `nil` is present, that likely means a problem parsing the metadata + ensure_not_present(test_name, "nil", output) + + ############################################################################ + # CodeBlock tests. # + ############################################################################ + begin_minted = r"\begin{{minted}}[{attrs}]{{{lang}}}" + verify( + "[code-block] default", + pandoc_args, + code_block, + begin_minted.format(attrs="autogobble", lang="cpp") + ) + verify( + "[code-block] no_default_autogobble", + pandoc_args, + textwrap.dedent(''' + --- + minted: + no_default_autogobble: true + --- + {code_block} + ''').format(code_block=code_block), + begin_minted.format(attrs="", lang="cpp") + ) + verify( + "[code-block] default block language is 'text'", + pandoc_args, + code_block.replace("{.cpp}", ""), + begin_minted.format(attrs="autogobble", lang="text") + ) + verify( + "[code-block] user provided default_block_language", + pandoc_args, + textwrap.dedent(''' + --- + minted: + default_block_language: "haskell" + --- + {code_block} + ''').format(code_block=code_block.replace("{.cpp}", "")), + begin_minted.format(attrs="autogobble", lang="haskell") + ) + verify( + "[code-block] user provided block_attributes", + pandoc_args, + textwrap.dedent(''' + --- + minted: + block_attributes: + - "showspaces" + - "space=." + --- + {code_block} + ''').format(code_block=code_block), + begin_minted.format( + attrs=",".join(["showspaces", "space=.", "autogobble"]), + lang="cpp" + ) + ) + verify( + "[code-block] user provided block_attributes and no_default_autogobble", + pandoc_args, + textwrap.dedent(''' + --- + minted: + no_default_autogobble: true + block_attributes: + - "style=monokai" + - "bgcolor=monokai_bg" + --- + {code_block} + ''').format(code_block=code_block), + begin_minted.format( + attrs=",".join(["style=monokai", "bgcolor=monokai_bg"]), lang="cpp" + ) + ) + verify( + "[code-block] attributes on code block", + pandoc_args, + code_block.replace( + "{.cpp}", "{.cpp .showspaces bgcolor=tango_bg style=tango}" + ), + begin_minted.format( + attrs=",".join([ + "showspaces", "bgcolor=tango_bg", "style=tango", "autogobble" + ]), + lang="cpp" + ) + ) + verify( + "[code-block] attributes on code block + user block_attributes", + pandoc_args, + textwrap.dedent(''' + --- + minted: + block_attributes: + - "showspaces" + - "space=." + --- + {code_block} + ''').format( + code_block=code_block.replace( + "{.cpp}", "{.cpp bgcolor=tango_bg style=tango}" + ) + ), + begin_minted.format( + attrs=",".join([ + "bgcolor=tango_bg", + "style=tango", + "showspaces", + "space=.", + "autogobble" + ]), + lang="cpp" + ) + ) + verify( + "[code-block] traditional fenced code block", + pandoc_args, + code_block.replace("{.cpp}", "cpp"), + begin_minted.format(attrs="autogobble", lang="cpp") + ) + verify( + "[code-block] non-minted attributes not forwarded", + pandoc_args, + code_block.replace("{.cpp}", "{.cpp .showspaces .hello}"), + begin_minted.format( + attrs=",".join(["showspaces", "autogobble"]), lang="cpp" + ) + ) + + ############################################################################ + # Inline Code tests. # + ############################################################################ + mintinline = r"\mintinline[{attrs}]{{{lang}}}" + verify( + "[inline-code] default", + pandoc_args, + inline_code, + mintinline.format(attrs="", lang="cpp"), + "|{|", + "|}|", + *[ + delim + '{' + inline_delims[:i] + delim + for i, delim in enumerate(inline_delims) + ] + ) + verify( + "[inline-code] default language is text", + pandoc_args, + inline_code, + mintinline.format(attrs="", lang="text"), + "|{|", + "|}|" + ) + # begin: global no_mintinline shared testing with / without --no-highlight + inline_no_mintinline_globally_md = textwrap.dedent(''' + --- + minted: + no_mintinline: true + --- + {inline_code} + ''').format(inline_code=inline_code) + inline_no_mintinline_globally_strings = [ + r"\texttt{\{}", + r"\texttt{\}}", + (r"\texttt{" + + r"\textasciitilde{}!@\#\$\%\^{}\&*()-=\_+{[}{]}\textbackslash{}\{\}" + + r"""\textbar{};\textquotesingle{}:",./\textless{}\textgreater{}?}""") + ] + verify( + "[inline-code] no_mintinline off globally", + pandoc_args, + inline_no_mintinline_globally_md, + r"\texttt{\#include\ \textless{}type\_traits\textgreater{}}", + *inline_no_mintinline_globally_strings + ) + verify( + "[inline-code] no_mintinline off globally, remove --no-highlight", + [arg for arg in pandoc_args if arg != "--no-highlight"], + inline_no_mintinline_globally_md, + r"\VERB|\PreprocessorTok{#include }\ImportTok{<type_traits>}|", + *inline_no_mintinline_globally_strings + ) + # end: global no_mintinline shared testing with / without --no-highlight + # begin: no_minted shared testing with / without --no-highlight + inline_no_minted_md = inline_code.replace("{.cpp}", "{.cpp .no_minted}") + inline_no_minted_strings = ["|{|", "|}|"] + verify( + "[inline-code] .no_minted on single inline Code", + pandoc_args, + inline_no_minted_md, + r"texttt{\#include\ \textless{}type\_traits\textgreater{}}", + *inline_no_minted_strings + ) + verify( + "[inline-code] .no_minted on single inline Code, remove --no-highlight", + [arg for arg in pandoc_args if arg != "--no-highlight"], + inline_no_minted_md, + r"\VERB|\PreprocessorTok{#include }\ImportTok{<type_traits>}|", + *inline_no_minted_strings + ) + # end: no_minted shared testing with / without --no-highlight + verify( + "[inline-code] user provided default_inline_language", + pandoc_args, + textwrap.dedent(''' + --- + minted: + default_inline_language: "haskell" + --- + {inline_code} + ''').format(inline_code=inline_code), + mintinline.format(attrs="", lang="haskell") + ) + verify( + "[inline-code] user provided inline_attributes", + pandoc_args, + textwrap.dedent(''' + --- + minted: + inline_attributes: + - "showspaces" + - "space=." + --- + {inline_code} + ''').format(inline_code=inline_code), + mintinline.format( + attrs=",".join(["showspaces", "space=."]), lang="cpp" + ), + mintinline.format( + attrs=",".join(["showspaces", "space=."]), lang="text" + ) + ) + verify( + "[inline-code] attributes on inline code", + pandoc_args, + inline_code.replace( + "{.cpp}", "{.cpp .showspaces bgcolor=tango_bg style=tango}" + ), + mintinline.format( + attrs=",".join(["showspaces", "bgcolor=tango_bg", "style=tango"]), + lang="cpp" + ) + ) + verify( + "[inline-code] attributes on inline code + user inline_attributes", + pandoc_args, + textwrap.dedent(''' + --- + minted: + inline_attributes: + - "showspaces" + - "space=." + --- + {inline_code} + ''').format( + inline_code=inline_code.replace( + "{.cpp}", "{.cpp bgcolor=tango_bg style=tango}" + ) + ), + mintinline.format( + attrs=",".join([ + "bgcolor=tango_bg", + "style=tango", + "showspaces", + "space=." + ]), + lang="cpp" + ) + ) + verify( + "[inline-code] non-minted attributes not forwarded", + pandoc_args, + inline_code.replace("{.cpp}", "{.cpp .showspaces .hello}"), + mintinline.format(attrs="showspaces", lang="cpp") + ) + + +def run_html_tests(args): + """ + Run tests with an html5 writer to make sure minted commands are not used. + Also make sure minted specific attributes are indeed stripped. + + ``args`` (list of str) + The base list of arguments to forward to pandoc. + """ + def verify(test_name, md, attrs=[]): + """Verify minted and any strings in attrs not produced""" + output = run_pandoc(args + ["-t", "html5"], md) + ensure_not_present(test_name, "mint", output) + ensure_not_present(test_name, "fragile", output) + if attrs: + for a in attrs: + ensure_not_present(test_name, a, output) + # if `nil` is present, that likely means a problem parsing the metadata + ensure_not_present(test_name, "nil", output) + + verify(r"[html] no \begin{minted}", code_block) + verify(r"[html] no \mintinline", inline_code) + verify( + r"[html] no \begin{minted} or \mintinline", + "{code_block}\n\n{inline_code}".format( + code_block=code_block, inline_code=inline_code + ) + ) + verify( + "[html] code block minted specific attributes stripped", + code_block.replace( + "{.cpp}", + "{.cpp .showspaces space=. bgcolor=minted_bg style=minted}" + ), + ["showspaces", "space", "bgcolor", "style"] + ) + verify( + "[html] inline code minted specific attributes stripped", + inline_code.replace( + "{.cpp}", + "{.cpp .showspaces space=. bgcolor=minted_bg style=minted}" + ), + ["showspaces", "space", "bgcolor", "style"] + ) + + +if __name__ == "__main__": + # Initial path setup for input tests and lua filter + this_file_dir = os.path.abspath(os.path.dirname(__file__)) + minted_lua = os.path.join(this_file_dir, "minted.lua") + if not os.path.isfile(minted_lua): + sys.stderr.write("Cannot find '{minted_lua}'...".format( + minted_lua=minted_lua + )) + sys.exit(1) + + args = ["--fail-if-warnings", "--no-highlight", "--lua-filter", minted_lua] + run_tex_tests(args, "beamer") + run_tex_tests(args, "latex") + run_html_tests(args) |