#!/usr/bin/python """ Python-Markdown Regression Tests ================================ Tests of the various APIs with the python markdown lib. """ from __future__ import unicode_literals import unittest import sys import os import markdown import warnings from markdown.__main__ import parse_options from logging import DEBUG, WARNING, CRITICAL import yaml import tempfile from io import BytesIO PY3 = sys.version_info[0] == 3 if not PY3: def bytes(string, encoding): return string.encode(encoding) class TestMarkdownBasics(unittest.TestCase): """ Tests basics of the Markdown class. """ def setUp(self): """ Create instance of Markdown. """ self.md = markdown.Markdown() def testBlankInput(self): """ Test blank input. """ self.assertEqual(self.md.convert(''), '') def testWhitespaceOnly(self): """ Test input of only whitespace. """ self.assertEqual(self.md.convert(' '), '') def testSimpleInput(self): """ Test simple input. """ self.assertEqual(self.md.convert('foo'), '

foo

') def testInstanceExtension(self): """ Test Extension loading with a class instance. """ from markdown.extensions.footnotes import FootnoteExtension markdown.Markdown(extensions=[FootnoteExtension()]) def testEntryPointExtension(self): """ Test Extension loading with an entry point. """ markdown.Markdown(extensions=['footnotes']) def testDotNotationExtension(self): """ Test Extension loading with Name (`path.to.module`). """ markdown.Markdown(extensions=['markdown.extensions.footnotes']) def testDotNotationExtensionWithClass(self): """ Test Extension loading with class name (`path.to.module:Class`). """ markdown.Markdown(extensions=['markdown.extensions.footnotes:FootnoteExtension']) class TestConvertFile(unittest.TestCase): """ Tests of ConvertFile. """ def setUp(self): self.saved = sys.stdin, sys.stdout sys.stdin = BytesIO(bytes('foo', encoding='utf-8')) sys.stdout = BytesIO() def tearDown(self): sys.stdin, sys.stdout = self.saved def getTempFiles(self, src): """ Return the file names for two temp files. """ infd, infile = tempfile.mkstemp(suffix='.txt') with os.fdopen(infd, 'w') as fp: fp.write(src) outfd, outfile = tempfile.mkstemp(suffix='.html') return infile, outfile, outfd def testFileNames(self): infile, outfile, outfd = self.getTempFiles('foo') markdown.markdownFromFile(input=infile, output=outfile) with os.fdopen(outfd, 'r') as fp: output = fp.read() self.assertEqual(output, '

foo

') def testFileObjects(self): infile = BytesIO(bytes('foo', encoding='utf-8')) outfile = BytesIO() markdown.markdownFromFile(input=infile, output=outfile) outfile.seek(0) self.assertEqual(outfile.read().decode('utf-8'), '

foo

') def testStdinStdout(self): markdown.markdownFromFile() sys.stdout.seek(0) self.assertEqual(sys.stdout.read().decode('utf-8'), '

foo

') class TestBlockParser(unittest.TestCase): """ Tests of the BlockParser class. """ def setUp(self): """ Create instance of BlockParser. """ self.parser = markdown.Markdown().parser def testParseChunk(self): """ Test BlockParser.parseChunk. """ root = markdown.util.etree.Element("div") text = 'foo' self.parser.parseChunk(root, text) self.assertEqual( markdown.serializers.to_xhtml_string(root), "

foo

" ) def testParseDocument(self): """ Test BlockParser.parseDocument. """ lines = ['#foo', '', 'bar', '', ' baz'] tree = self.parser.parseDocument(lines) self.assertTrue(isinstance(tree, markdown.util.etree.ElementTree)) self.assertTrue(markdown.util.etree.iselement(tree.getroot())) self.assertEqual( markdown.serializers.to_xhtml_string(tree.getroot()), "

foo

bar

baz\n
" ) class TestBlockParserState(unittest.TestCase): """ Tests of the State class for BlockParser. """ def setUp(self): self.state = markdown.blockparser.State() def testBlankState(self): """ Test State when empty. """ self.assertEqual(self.state, []) def testSetSate(self): """ Test State.set(). """ self.state.set('a_state') self.assertEqual(self.state, ['a_state']) self.state.set('state2') self.assertEqual(self.state, ['a_state', 'state2']) def testIsSate(self): """ Test State.isstate(). """ self.assertEqual(self.state.isstate('anything'), False) self.state.set('a_state') self.assertEqual(self.state.isstate('a_state'), True) self.state.set('state2') self.assertEqual(self.state.isstate('state2'), True) self.assertEqual(self.state.isstate('a_state'), False) self.assertEqual(self.state.isstate('missing'), False) def testReset(self): """ Test State.reset(). """ self.state.set('a_state') self.state.reset() self.assertEqual(self.state, []) self.state.set('state1') self.state.set('state2') self.state.reset() self.assertEqual(self.state, ['state1']) class TestHtmlStash(unittest.TestCase): """ Test Markdown's HtmlStash. """ def setUp(self): self.stash = markdown.util.HtmlStash() self.placeholder = self.stash.store('foo') def testSimpleStore(self): """ Test HtmlStash.store. """ self.assertEqual(self.placeholder, self.stash.get_placeholder(0)) self.assertEqual(self.stash.html_counter, 1) self.assertEqual(self.stash.rawHtmlBlocks, ['foo']) def testStoreMore(self): """ Test HtmlStash.store with additional blocks. """ placeholder = self.stash.store('bar') self.assertEqual(placeholder, self.stash.get_placeholder(1)) self.assertEqual(self.stash.html_counter, 2) self.assertEqual( self.stash.rawHtmlBlocks, ['foo', 'bar'] ) def testReset(self): """ Test HtmlStash.reset. """ self.stash.reset() self.assertEqual(self.stash.html_counter, 0) self.assertEqual(self.stash.rawHtmlBlocks, []) class TestOrderedDict(unittest.TestCase): """ Test OrderedDict storage class. """ def setUp(self): self.odict = markdown.odict.OrderedDict() self.odict['first'] = 'This' self.odict['third'] = 'a' self.odict['fourth'] = 'self' self.odict['fifth'] = 'test' def testValues(self): """ Test output of OrderedDict.values(). """ self.assertEqual(list(self.odict.values()), ['This', 'a', 'self', 'test']) def testKeys(self): """ Test output of OrderedDict.keys(). """ self.assertEqual( list(self.odict.keys()), ['first', 'third', 'fourth', 'fifth'] ) def testItems(self): """ Test output of OrderedDict.items(). """ self.assertEqual( list(self.odict.items()), [ ('first', 'This'), ('third', 'a'), ('fourth', 'self'), ('fifth', 'test') ] ) def testAddBefore(self): """ Test adding an OrderedDict item before a given key. """ self.odict.add('second', 'is', 'first') self.assertEqual( list(self.odict.items()), [ ('first', 'This'), ('second', 'is'), ('third', 'a'), ('fourth', 'self'), ('fifth', 'test') ] ) def testAddAfterEnd(self): """ Test adding an OrderedDict item after the last key. """ self.odict.add('sixth', '.', '>fifth') self.assertEqual( list(self.odict.items()), [ ('first', 'This'), ('third', 'a'), ('fourth', 'self'), ('fifth', 'test'), ('sixth', '.') ] ) def testAdd_begin(self): """ Test adding an OrderedDict item using "_begin". """ self.odict.add('zero', 'CRAZY', '_begin') self.assertEqual( list(self.odict.items()), [ ('zero', 'CRAZY'), ('first', 'This'), ('third', 'a'), ('fourth', 'self'), ('fifth', 'test') ] ) def testAdd_end(self): """ Test adding an OrderedDict item using "_end". """ self.odict.add('sixth', '.', '_end') self.assertEqual( list(self.odict.items()), [ ('first', 'This'), ('third', 'a'), ('fourth', 'self'), ('fifth', 'test'), ('sixth', '.') ] ) def testAddBadLocation(self): """ Test Error on bad location in OrderedDict.add(). """ self.assertRaises(ValueError, self.odict.add, 'sixth', '.', '' ) def testCommentPrettify(self): """ Test that an ElementTree Comment is prettified properly. """ pretty = markdown.treeprocessors.PrettifyTreeprocessor() pretty.run(self.comment) self.assertEqual( markdown.serializers.to_html_string(self.comment), '\n' ) class testElementTailTests(unittest.TestCase): """ Element Tail Tests """ def setUp(self): self.pretty = markdown.treeprocessors.PrettifyTreeprocessor() def testBrTailNoNewline(self): """ Test that last
in tree has a new line tail """ root = markdown.util.etree.Element('root') br = markdown.util.etree.SubElement(root, 'br') self.assertEqual(br.tail, None) self.pretty.run(root) self.assertEqual(br.tail, "\n") class testSerializers(unittest.TestCase): """ Test the html and xhtml serializers. """ def testHtml(self): """ Test HTML serialization. """ el = markdown.util.etree.Element('div') p = markdown.util.etree.SubElement(el, 'p') p.text = 'foo' markdown.util.etree.SubElement(el, 'hr') self.assertEqual( markdown.serializers.to_html_string(el), '

foo


' ) def testXhtml(self): """" Test XHTML serialization. """ el = markdown.util.etree.Element('div') p = markdown.util.etree.SubElement(el, 'p') p.text = 'foo' markdown.util.etree.SubElement(el, 'hr') self.assertEqual( markdown.serializers.to_xhtml_string(el), '

foo


' ) def testMixedCaseTags(self): """" Test preservation of tag case. """ el = markdown.util.etree.Element('MixedCase') el.text = 'not valid ' em = markdown.util.etree.SubElement(el, 'EMPHASIS') em.text = 'html' markdown.util.etree.SubElement(el, 'HR') self.assertEqual( markdown.serializers.to_xhtml_string(el), 'not valid html
' ) def buildExtension(self): """ Build an extension which registers fakeSerializer. """ def fakeSerializer(elem): # Ignore input and return hardcoded output return '

foo

' class registerFakeSerializer(markdown.extensions.Extension): def extendMarkdown(self, md, md_globals): md.output_formats['fake'] = fakeSerializer return registerFakeSerializer() def testRegisterSerializer(self): self.assertEqual( markdown.markdown( 'baz', extensions=[self.buildExtension()], output_format='fake' ), '

foo

' ) class testAtomicString(unittest.TestCase): """ Test that AtomicStrings are honored (not parsed). """ def setUp(self): md = markdown.Markdown() self.inlineprocessor = md.treeprocessors['inline'] def testString(self): """ Test that a regular string is parsed. """ tree = markdown.util.etree.Element('div') p = markdown.util.etree.SubElement(tree, 'p') p.text = 'some *text*' new = self.inlineprocessor.run(tree) self.assertEqual( markdown.serializers.to_html_string(new), '

some text

' ) def testSimpleAtomicString(self): """ Test that a simple AtomicString is not parsed. """ tree = markdown.util.etree.Element('div') p = markdown.util.etree.SubElement(tree, 'p') p.text = markdown.util.AtomicString('some *text*') new = self.inlineprocessor.run(tree) self.assertEqual( markdown.serializers.to_html_string(new), '

some *text*

' ) def testNestedAtomicString(self): """ Test that a nested AtomicString is not parsed. """ tree = markdown.util.etree.Element('div') p = markdown.util.etree.SubElement(tree, 'p') p.text = markdown.util.AtomicString('*some* ') span1 = markdown.util.etree.SubElement(p, 'span') span1.text = markdown.util.AtomicString('*more* ') span2 = markdown.util.etree.SubElement(span1, 'span') span2.text = markdown.util.AtomicString('*text* ') span3 = markdown.util.etree.SubElement(span2, 'span') span3.text = markdown.util.AtomicString('*here*') span3.tail = markdown.util.AtomicString(' *to*') span2.tail = markdown.util.AtomicString(' *test*') span1.tail = markdown.util.AtomicString(' *with*') new = self.inlineprocessor.run(tree) self.assertEqual( markdown.serializers.to_html_string(new), '

*some* *more* *text* *here* ' '*to* *test* *with*

' ) class TestConfigParsing(unittest.TestCase): def assertParses(self, value, result): self.assertTrue(markdown.util.parseBoolValue(value, False) is result) def testBooleansParsing(self): self.assertParses(True, True) self.assertParses('novalue', None) self.assertParses('yES', True) self.assertParses('FALSE', False) self.assertParses(0., False) self.assertParses('none', False) def testPreserveNone(self): self.assertTrue(markdown.util.parseBoolValue('None', preserve_none=True) is None) self.assertTrue(markdown.util.parseBoolValue(None, preserve_none=True) is None) def testInvalidBooleansParsing(self): self.assertRaises(ValueError, markdown.util.parseBoolValue, 'novalue') class TestCliOptionParsing(unittest.TestCase): """ Test parsing of Command Line Interface Options. """ def setUp(self): self.default_options = { 'input': None, 'output': None, 'encoding': None, 'output_format': 'xhtml', 'lazy_ol': True, 'extensions': [], 'extension_configs': {}, } self.tempfile = '' def tearDown(self): if os.path.isfile(self.tempfile): os.remove(self.tempfile) def testNoOptions(self): options, logging_level = parse_options([]) self.assertEqual(options, self.default_options) self.assertEqual(logging_level, CRITICAL) def testQuietOption(self): options, logging_level = parse_options(['-q']) self.assertTrue(logging_level > CRITICAL) def testVerboseOption(self): options, logging_level = parse_options(['-v']) self.assertEqual(logging_level, WARNING) def testNoisyOption(self): options, logging_level = parse_options(['--noisy']) self.assertEqual(logging_level, DEBUG) def testInputFileOption(self): options, logging_level = parse_options(['foo.txt']) self.default_options['input'] = 'foo.txt' self.assertEqual(options, self.default_options) def testOutputFileOption(self): options, logging_level = parse_options(['-f', 'foo.html']) self.default_options['output'] = 'foo.html' self.assertEqual(options, self.default_options) def testInputAndOutputFileOptions(self): options, logging_level = parse_options(['-f', 'foo.html', 'foo.txt']) self.default_options['output'] = 'foo.html' self.default_options['input'] = 'foo.txt' self.assertEqual(options, self.default_options) def testEncodingOption(self): options, logging_level = parse_options(['-e', 'utf-8']) self.default_options['encoding'] = 'utf-8' self.assertEqual(options, self.default_options) def testOutputFormatOption(self): options, logging_level = parse_options(['-o', 'html']) self.default_options['output_format'] = 'html' self.assertEqual(options, self.default_options) def testNoLazyOlOption(self): options, logging_level = parse_options(['-n']) self.default_options['lazy_ol'] = False self.assertEqual(options, self.default_options) def testExtensionOption(self): options, logging_level = parse_options(['-x', 'markdown.extensions.footnotes']) self.default_options['extensions'] = ['markdown.extensions.footnotes'] self.assertEqual(options, self.default_options) def testMultipleExtensionOptions(self): options, logging_level = parse_options([ '-x', 'markdown.extensions.footnotes', '-x', 'markdown.extensions.smarty' ]) self.default_options['extensions'] = [ 'markdown.extensions.footnotes', 'markdown.extensions.smarty' ] self.assertEqual(options, self.default_options) def create_config_file(self, config): """ Helper to create temp config files. """ if not isinstance(config, markdown.util.string_type): # convert to string config = yaml.dump(config) fd, self.tempfile = tempfile.mkstemp('.yml') with os.fdopen(fd, 'w') as fp: fp.write(config) def testExtensionConfigOption(self): config = { 'markdown.extensions.wikilinks': { 'base_url': 'http://example.com/', 'end_url': '.html', 'html_class': 'test', }, 'markdown.extensions.footnotes:FootnotesExtension': { 'PLACE_MARKER': '~~~footnotes~~~' } } self.create_config_file(config) options, logging_level = parse_options(['-c', self.tempfile]) self.default_options['extension_configs'] = config self.assertEqual(options, self.default_options) def textBoolExtensionConfigOption(self): config = { 'markdown.extensions.toc': { 'title': 'Some Title', 'anchorlink': True, 'permalink': True } } self.create_config_file(config) options, logging_level = parse_options(['-c', self.tempfile]) self.default_options['extension_configs'] = config self.assertEqual(options, self.default_options) def testExtensonConfigOptionAsJSON(self): config = { 'markdown.extensions.wikilinks': { 'base_url': 'http://example.com/', 'end_url': '.html', 'html_class': 'test', }, 'markdown.extensions.footnotes:FootnotesExtension': { 'PLACE_MARKER': '~~~footnotes~~~' } } import json self.create_config_file(json.dumps(config)) options, logging_level = parse_options(['-c', self.tempfile]) self.default_options['extension_configs'] = config self.assertEqual(options, self.default_options) def testExtensonConfigOptionMissingFile(self): self.assertRaises(IOError, parse_options, ['-c', 'missing_file.yaml']) def testExtensonConfigOptionBadFormat(self): config = """ [footnotes] PLACE_MARKER= ~~~footnotes~~~ """ self.create_config_file(config) self.assertRaises(yaml.YAMLError, parse_options, ['-c', self.tempfile]) class TestEscapeAppend(unittest.TestCase): """ Tests escape character append. """ def testAppend(self): """ Test that appended escapes are only in the current instance. """ md = markdown.Markdown() md.ESCAPED_CHARS.append('|') self.assertEqual('|' in md.ESCAPED_CHARS, True) md2 = markdown.Markdown() self.assertEqual('|' not in md2.ESCAPED_CHARS, True) class TestAncestorExclusion(unittest.TestCase): """ Tests exclusion of tags in ancestor list. """ class AncestorExample(markdown.inlinepatterns.SimpleTagInlineProcessor): """ Ancestor Test. """ ANCESTOR_EXCLUDES = ('a',) def handleMatch(self, m, data): """ Handle match. """ el = markdown.util.etree.Element(self.tag) el.text = m.group(2) return el, m.start(0), m.end(0) class AncestorExtension(markdown.Extension): def __init__(self, *args, **kwargs): """Initialize.""" self.config = {} def extendMarkdown(self, md, md_globals): """Modify inline patterns.""" pattern = r'(\+)([^\+]+)\1' md.inlinePatterns["ancestor-test"] = TestAncestorExclusion.AncestorExample(pattern, 'strong') def setUp(self): """Setup markdown object.""" self.md = markdown.Markdown(extensions=[TestAncestorExclusion.AncestorExtension()]) def test_ancestors(self): """ Test that an extension can exclude parent tags. """ test = """ Some +test+ and a [+link+](http://test.com) """ result = """

Some test and a +link+

""" self.md.reset() self.assertEqual(self.md.convert(test), result) def test_ancestors_tail(self): """ Test that an extension can exclude parent tags when dealing with a tail. """ test = """ [***+em+*+strong+**](http://test.com) """ result = """

+em++strong+

""" self.md.reset() self.assertEqual(self.md.convert(test), result)