diff --git a/Makefile b/Makefile index fb0f79c..9faec03 100644 --- a/Makefile +++ b/Makefile @@ -46,7 +46,7 @@ src/html_blocks.c: html_block_names.gperf # Testing test: hoedown - test/runner.sh ./hoedown test/MarkdownTest_1.0.3/Tests + python test/runner.py test-pl: hoedown perl test/MarkdownTest_1.0.3/MarkdownTest.pl \ diff --git a/test/MarkdownTest_1.0.3/Tests/Escape character.html b/test/Tests/Escape character.html similarity index 100% rename from test/MarkdownTest_1.0.3/Tests/Escape character.html rename to test/Tests/Escape character.html diff --git a/test/MarkdownTest_1.0.3/Tests/Escape character.text b/test/Tests/Escape character.text similarity index 100% rename from test/MarkdownTest_1.0.3/Tests/Escape character.text rename to test/Tests/Escape character.text diff --git a/test/Tests/Math.html b/test/Tests/Math.html new file mode 100644 index 0000000..5e2fd3e --- /dev/null +++ b/test/Tests/Math.html @@ -0,0 +1,31 @@ +

\[ +1*2*3 multi-line math +\]

+ +

\( 1*2*3 inline-math \)

+ +

\[ 1*2*3 math with dollar \]

+ +

\[ 1*2*3 \$ \\ \text{dollar with escapes} \]

+ +

\( \\ \text{backslash with escapes} \$ 1*2*3 \)

+ +

( not really math )

+ +

$$ also not math $$

+ +

this$$ should not be$$ math

+ +

nor $$ should $$this

+ +

this \(*should* be\) math

+ +

Something \{ like math but \} is not

+ +

Also \(like math but \) is not

+ +

\\( should not be math either \\)

+ +

This is \( math, and the \\\( inner one \\\) should be \) preserved

+ +

\[ did you <em> know </em> this is math? \]

diff --git a/test/Tests/Math.text b/test/Tests/Math.text new file mode 100644 index 0000000..789c213 --- /dev/null +++ b/test/Tests/Math.text @@ -0,0 +1,31 @@ +\\[ +1*2*3 multi-line math +\\] + +\\( 1*2*3 inline-math \\) + +$$ 1*2*3 math with dollar $$ + +$$ 1*2*3 \$ \\ \text{dollar with escapes} $$ + +\\( \\ \text{backslash with escapes} \$ 1*2*3 \\) + +\( not *really* math \) + +\$$ also *not* math \$$ + +this$$ should *not* be$$ math + +nor $$ *should* $$this + +this $$*should* be$$ math + +Something \\{ like *math* but \\} is not + +Also \\\(like *math* but \\\) is not + +\\\\( should not be *math* either \\\\) + +This is \\( math, and the \\\( inner one \\\) should be \\) preserved + +$$ did you know this is math? $$ diff --git a/test/config.json b/test/config.json new file mode 100644 index 0000000..63cb1d7 --- /dev/null +++ b/test/config.json @@ -0,0 +1,101 @@ +{ + "tests": [ + { + "input": "MarkdownTest_1.0.3/Tests/Amps and angle encoding.text", + "output": "MarkdownTest_1.0.3/Tests/Amps and angle encoding.html" + }, + { + "input": "MarkdownTest_1.0.3/Tests/Auto links.text", + "output": "MarkdownTest_1.0.3/Tests/Auto links.html" + }, + { + "input": "MarkdownTest_1.0.3/Tests/Backslash escapes.text", + "output": "MarkdownTest_1.0.3/Tests/Backslash escapes.html" + }, + { + "input": "MarkdownTest_1.0.3/Tests/Blockquotes with code blocks.text", + "output": "MarkdownTest_1.0.3/Tests/Blockquotes with code blocks.html" + }, + { + "input": "MarkdownTest_1.0.3/Tests/Code Blocks.text", + "output": "MarkdownTest_1.0.3/Tests/Code Blocks.html" + }, + { + "input": "MarkdownTest_1.0.3/Tests/Code Spans.text", + "output": "MarkdownTest_1.0.3/Tests/Code Spans.html" + }, + { + "input": "MarkdownTest_1.0.3/Tests/Hard-wrapped paragraphs with list-like lines.text", + "output": "MarkdownTest_1.0.3/Tests/Hard-wrapped paragraphs with list-like lines.html" + }, + { + "input": "MarkdownTest_1.0.3/Tests/Horizontal rules.text", + "output": "MarkdownTest_1.0.3/Tests/Horizontal rules.html" + }, + { + "input": "MarkdownTest_1.0.3/Tests/Inline HTML (Advanced).text", + "output": "MarkdownTest_1.0.3/Tests/Inline HTML (Advanced).html" + }, + { + "input": "MarkdownTest_1.0.3/Tests/Inline HTML (Simple).text", + "output": "MarkdownTest_1.0.3/Tests/Inline HTML (Simple).html" + }, + { + "input": "MarkdownTest_1.0.3/Tests/Inline HTML comments.text", + "output": "MarkdownTest_1.0.3/Tests/Inline HTML comments.html" + }, + { + "input": "MarkdownTest_1.0.3/Tests/Links, inline style.text", + "output": "MarkdownTest_1.0.3/Tests/Links, inline style.html" + }, + { + "input": "MarkdownTest_1.0.3/Tests/Links, reference style.text", + "output": "MarkdownTest_1.0.3/Tests/Links, reference style.html" + }, + { + "input": "MarkdownTest_1.0.3/Tests/Links, shortcut references.text", + "output": "MarkdownTest_1.0.3/Tests/Links, shortcut references.html" + }, + { + "input": "MarkdownTest_1.0.3/Tests/Literal quotes in titles.text", + "output": "MarkdownTest_1.0.3/Tests/Literal quotes in titles.html" + }, + { + "input": "MarkdownTest_1.0.3/Tests/Markdown Documentation - Basics.text", + "output": "MarkdownTest_1.0.3/Tests/Markdown Documentation - Basics.html" + }, + { + "input": "MarkdownTest_1.0.3/Tests/Markdown Documentation - Syntax.text", + "output": "MarkdownTest_1.0.3/Tests/Markdown Documentation - Syntax.html" + }, + { + "input": "MarkdownTest_1.0.3/Tests/Nested blockquotes.text", + "output": "MarkdownTest_1.0.3/Tests/Nested blockquotes.html" + }, + { + "input": "MarkdownTest_1.0.3/Tests/Ordered and unordered lists.text", + "output": "MarkdownTest_1.0.3/Tests/Ordered and unordered lists.html" + }, + { + "input": "MarkdownTest_1.0.3/Tests/Strong and em together.text", + "output": "MarkdownTest_1.0.3/Tests/Strong and em together.html" + }, + { + "input": "MarkdownTest_1.0.3/Tests/Tabs.text", + "output": "MarkdownTest_1.0.3/Tests/Tabs.html" + }, + { + "input": "MarkdownTest_1.0.3/Tests/Tidyness.text", + "output": "MarkdownTest_1.0.3/Tests/Tidyness.html" + }, + { + "input": "Tests/Escape character.text", + "output": "Tests/Escape character.html" + }, + { + "input": "Tests/Math.text", + "output": "Tests/Math.html", + "flags": ["--math"] + } + ] +} diff --git a/test/runner.py b/test/runner.py new file mode 100755 index 0000000..4102fad --- /dev/null +++ b/test/runner.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import difflib +import json +import os +import re +import subprocess +import unittest + +TEST_ROOT = os.path.dirname(__file__) +PROJECT_ROOT = os.path.dirname(TEST_ROOT) +HOEDOWN = [os.path.abspath(os.path.join(PROJECT_ROOT, 'hoedown'))] +TIDY = ['tidy', '--show-body-only', '1', '--show-warnings', '0', + '--quiet', '1'] +CONFIG_PATH = os.path.join(TEST_ROOT, 'config.json') +SLUGIFY_PATTERN = re.compile(r'\W') + + +def with_metaclass(meta, *bases): + """Metaclass injection utility from six. + + See: https://pythonhosted.org/six/ + """ + class metaclass(meta): + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + return type.__new__(metaclass, 'temporary_class', (), {}) + + +class TestFailed(AssertionError): + def __init__(self, name, expected, got): + super(TestFailed, self).__init__(self) + diff = difflib.unified_diff( + expected.splitlines(), got.splitlines(), + fromfile='Expected', tofile='Got', + ) + self.description = '{name}\n{diff}'.format( + name=name, diff='\n'.join(diff), + ) + + def __str__(self): + return self.description + + +def _test_func(test_case): + flags = test_case.get('flags') or [] + hoedown_proc = subprocess.Popen( + HOEDOWN + flags + [os.path.join(TEST_ROOT, test_case['input'])], + stdout=subprocess.PIPE, + ) + hoedown_proc.wait() + got_tidy_proc = subprocess.Popen( + TIDY, stdin=hoedown_proc.stdout, stdout=subprocess.PIPE, + ) + got_tidy_proc.wait() + got = got_tidy_proc.stdout.read().strip() + + expected_tidy_proc = subprocess.Popen( + TIDY + [os.path.join(TEST_ROOT, test_case['output'])], + stdout=subprocess.PIPE, + ) + expected_tidy_proc.wait() + expected = expected_tidy_proc.stdout.read().strip() + + # Cleanup. + hoedown_proc.stdout.close() + got_tidy_proc.stdout.close() + expected_tidy_proc.stdout.close() + + try: + assert expected == got + except AssertionError: + raise TestFailed(test_case['input'], expected, got) + + +def _make_test(test_case): + return lambda self: _test_func(test_case) + + +class MarkdownTestsMeta(type): + """Meta class for ``MarkdownTestCase`` to inject test cases on the fly. + """ + def __new__(meta, name, bases, attrs): + with open(CONFIG_PATH) as f: + config = json.load(f) + + for test in config['tests']: + input_name = test['input'] + attr_name = 'test_' + SLUGIFY_PATTERN.sub( + '_', os.path.splitext(input_name)[0].lower(), + ) + func = _make_test(test) + func.__doc__ = input_name + if test.get('skip', False): + func = unittest.skip(input_name)(func) + if test.get('fail', False): + func = unittest.expectsFailure(func) + attrs[attr_name] = func + return type.__new__(meta, name, bases, attrs) + + +class MarkdownTests(with_metaclass(MarkdownTestsMeta, unittest.TestCase)): + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/test/runner.sh b/test/runner.sh deleted file mode 100755 index d60eac5..0000000 --- a/test/runner.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/sh - -POSIXLY_CORRECT=1 -export POSIXLY_CORRECT - -TIDY='tidy --show-body-only 1 --quiet 1 --show-warnings 0' -SCRIPT="$1" -TESTDIR="$2" -PASSED=0 -FAILED=0 - -abort() { - echo "Error: $*" - exit 1 -} - -test -f "$SCRIPT" || abort "argument #1 invalid; not a file" -test -x "$SCRIPT" || abort "argument #1 invalid; not executable" -echo "" | "$SCRIPT" || abort "argument #1 invalid; script failed to run" -test -d "$TESTDIR" || abort "argument #2 invalid; not a directory" - -for TEXT in "$TESTDIR"/*.text; do - test -f "$TEXT" || abort "empty or invalid test directory" - printf "$(basename "$TEXT" .text) ... " - HTML=$(echo "$TEXT" | sed 's/\.text$/.html/') - - # We use mktemp to create an unpredictable, temporary filename. - # The created file is immediately deleted, since we only want a - # name to pass to mkfifo and "mktemp -u" is not portable. - PIPE=$(mktemp .testpipe-XXXXXXXX) - test -f "$PIPE" -a -n "$PIPE" || abort "mktemp failed" - trap 'rm -f "$PIPE"' EXIT INT TERM HUP - rm -f "$PIPE" - mkfifo -m 0600 "$PIPE" || abort "unable to create named pipe" - - $SCRIPT "$TEXT" | $TIDY > "$PIPE" & - DIFF=$($TIDY "$HTML" | diff "$PIPE" -) - if test "$?" = 0; then - PASSED=$(expr $PASSED + 1) - echo OK - else - FAILED=$(expr $FAILED + 1) - echo FAILED - printf "\n$DIFF\n\n" - fi - rm -f "$PIPE" -done - -printf "\n\n$PASSED passed; $FAILED failed.\n" -test "$FAILED" = 0 || exit 1