Merge pull request #122 from uranusjr/unittest
New test framework based on Python’s unittest module
This commit is contained in:
commit
d20dd8ee04
8 changed files with 272 additions and 51 deletions
2
Makefile
2
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 \
|
||||
|
|
31
test/Tests/Math.html
Normal file
31
test/Tests/Math.html
Normal file
|
@ -0,0 +1,31 @@
|
|||
<p>\[
|
||||
1*2*3 multi-line math
|
||||
\]</p>
|
||||
|
||||
<p>\( 1*2*3 inline-math \)</p>
|
||||
|
||||
<p>\[ 1*2*3 math with dollar \]</p>
|
||||
|
||||
<p>\[ 1*2*3 \$ \\ \text{dollar with escapes} \]</p>
|
||||
|
||||
<p>\( \\ \text{backslash with escapes} \$ 1*2*3 \)</p>
|
||||
|
||||
<p>( not <em>really</em> math )</p>
|
||||
|
||||
<p>$$ also <em>not</em> math $$</p>
|
||||
|
||||
<p>this$$ should <em>not</em> be$$ math</p>
|
||||
|
||||
<p>nor $$ <em>should</em> $$this</p>
|
||||
|
||||
<p>this \(*should* be\) math</p>
|
||||
|
||||
<p>Something \{ like <em>math</em> but \} is not</p>
|
||||
|
||||
<p>Also \(like <em>math</em> but \) is not</p>
|
||||
|
||||
<p>\\( should not be <em>math</em> either \\)</p>
|
||||
|
||||
<p>This is \( math, and the \\\( inner one \\\) should be \) preserved</p>
|
||||
|
||||
<p>\[ did you <em> know </em> this is math? \]</p>
|
31
test/Tests/Math.text
Normal file
31
test/Tests/Math.text
Normal file
|
@ -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? $$
|
101
test/config.json
Normal file
101
test/config.json
Normal file
|
@ -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"]
|
||||
}
|
||||
]
|
||||
}
|
108
test/runner.py
Executable file
108
test/runner.py
Executable file
|
@ -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()
|
|
@ -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
|
Loading…
Reference in a new issue