ソースを参照

Adding tests for broken/incomplete syntax

main
Rocketsoup 1年前
コミット
1fbb1d6514
5個のファイルの変更272行の追加7行の削除
  1. 120
    0
      jstest/BrokenSyntaxTests.js
  2. 17
    7
      php/markdown.php
  3. 132
    0
      phptest/BrokenSyntaxTests.php
  4. 1
    0
      runphptests.sh
  5. 2
    0
      testjs.html

+ 120
- 0
jstest/BrokenSyntaxTests.js ファイルの表示

@@ -0,0 +1,120 @@
1
+class BrokenSyntaxTests extends BaseTest {
2
+	#parser;
3
+
4
+	setUp() {
5
+		this.#parser = Markdown.completeParser;
6
+	}
7
+
8
+	/**
9
+	 * @param {string} markdown
10
+	 */
11
+	#iterateCharacters(markdown) {
12
+		for (var i = 1; i < markdown.length; i++) {
13
+			const portion = markdown.substring(0, i);
14
+			this.#parser.toHTML(portion);
15
+		}
16
+	}
17
+
18
+	test_brokenSyntax() {
19
+		// Just try a bunch of broken syntax and make sure no exceptions are
20
+		// thrown or infinite loops triggered. Don't care about what's rendered
21
+		// as most of these are incomplete or invalid syntax.
22
+		this.#iterateCharacters("# ");
23
+		this.#iterateCharacters("## ");
24
+		this.#iterateCharacters("Underlined\n-");
25
+		this.#iterateCharacters("* ");
26
+		this.#iterateCharacters(" * A");
27
+		this.#iterateCharacters("- A");
28
+		this.#iterateCharacters(" - A");
29
+		this.#iterateCharacters("+ A");
30
+		this.#iterateCharacters(" + A");
31
+		this.#iterateCharacters("1. A");
32
+		this.#iterateCharacters(" 1. A");
33
+		this.#iterateCharacters("1. A\n 1. B\n 2. C\n2. D");
34
+		this.#iterateCharacters("> A");
35
+		this.#iterateCharacters("> \nA");
36
+		this.#iterateCharacters("> \n>");
37
+		this.#iterateCharacters(">>");
38
+		this.#iterateCharacters("> > A");
39
+		this.#iterateCharacters("> \n> A");
40
+		this.#iterateCharacters("> > \n> A");
41
+		this.#iterateCharacters("```\ncode\n```");
42
+		this.#iterateCharacters("```java\ncode\n```");
43
+		this.#iterateCharacters("    code\n    code\n\n");
44
+		this.#iterateCharacters("------");
45
+		this.#iterateCharacters("  -  -    - --*");
46
+		this.#iterateCharacters("Header|Header\n--|--\nCell|Cell");
47
+		this.#iterateCharacters("Header\n--|--\nCell");
48
+		this.#iterateCharacters("|Header|\n|-|\n|Cell|");
49
+		this.#iterateCharacters("|Header|\n|--|--|--|\n|Cell|");
50
+		this.#iterateCharacters("|Header|Header|\n|--|\n|Cell|Cell|");
51
+		this.#iterateCharacters("|Header|\n|:-|\n|Cell|");
52
+		this.#iterateCharacters("|Header|\n|:-:|\n|Cell|");
53
+		this.#iterateCharacters("|Header|\n|-:|\n|Cell|");
54
+		this.#iterateCharacters("term\n: definition\nterm\n: definition");
55
+		this.#iterateCharacters("term\n:definition\nterm\n:definition");
56
+		this.#iterateCharacters("HTML\n\n*[HTML]: Hypertext");
57
+		this.#iterateCharacters("HTML\n*[HTML]: Hypertext");
58
+		this.#iterateCharacters("*[HTML]: Hypertext\nHTML");
59
+		this.#iterateCharacters("---{.foo}\n");
60
+		this.#iterateCharacters("---{#foo}\n");
61
+		this.#iterateCharacters("---{.foo #foo lang=en}\n");
62
+		this.#iterateCharacters("lorem *ipsum* dolor *sit* amet");
63
+		this.#iterateCharacters("*lorem* ipsum *dolor* sit *amet*");
64
+		this.#iterateCharacters("*lorem *ipsum *dolor*** sit");
65
+		this.#iterateCharacters("***lorem* ipsum* dolor* sit");
66
+		this.#iterateCharacters("lorem _ipsum_ dolor _sit_ amet");
67
+		this.#iterateCharacters("_lorem_ ipsum _dolor_ sit _amet_");
68
+		this.#iterateCharacters("_lorem _ipsum _dolor___ sit");
69
+		this.#iterateCharacters("___lorem_ ipsum_ dolor_ sit");
70
+		this.#iterateCharacters("lorem **ipsum** dolor **sit** amet");
71
+		this.#iterateCharacters("**lorem** ipsum **dolor** sit **amet**");
72
+		this.#iterateCharacters("**lorem **ipsum **dolor****** sit");
73
+		this.#iterateCharacters("******lorem** ipsum** dolor** sit");
74
+		this.#iterateCharacters("lorem __ipsum__ dolor __sit__ amet");
75
+		this.#iterateCharacters("__lorem__ ipsum __dolor__ sit __amet__");
76
+		this.#iterateCharacters("lorem __ipsum__ dolor __sit__ amet");
77
+		this.#iterateCharacters("__lorem __ipsum __dolor______ sit");
78
+		this.#iterateCharacters("______lorem__ ipsum__ dolor__ sit");
79
+		this.#iterateCharacters("*_**`~~^==!__~*^!``**-+!**`==!~_^**``_");
80
+		this.#iterateCharacters("[link[(index.html(");
81
+		this.#iterateCharacters("[link[](index.html()");
82
+		this.#iterateCharacters("[link]](index.html))");
83
+		this.#iterateCharacters("[[link]((index.html)");
84
+		this.#iterateCharacters("]link])index.html)");
85
+		this.#iterateCharacters("[ link ] ( index.html )");
86
+		this.#iterateCharacters("(user@example.com)");
87
+		this.#iterateCharacters("(https://user@example.com)");
88
+		this.#iterateCharacters("(index.html \"title\")");
89
+		this.#iterateCharacters("(index.html \"title)");
90
+		this.#iterateCharacters("(index.html title\")");
91
+		this.#iterateCharacters("(index.html title)");
92
+		this.#iterateCharacters("![alt][image.jpg]");
93
+		this.#iterateCharacters("![alt[](image.jpg()");
94
+		this.#iterateCharacters("! [alt](image.jpg)");
95
+		this.#iterateCharacters("!(image.jpg)");
96
+		this.#iterateCharacters("[][]]][][[][][][[][[]][][][][][[[]]]][]][][");
97
+		this.#iterateCharacters("())())()()()(())(()((())))(()((()");
98
+		this.#iterateCharacters("<https://example.com>");
99
+		this.#iterateCharacters("<<https://example.com>");
100
+		this.#iterateCharacters("<https://example.com>>");
101
+		this.#iterateCharacters("[link][ref]\n\n[ref]: page.html");
102
+		this.#iterateCharacters("[link][ref]\n[ref]: page.html");
103
+		this.#iterateCharacters("[link][ref]\n\n[ref]:page.html");
104
+		this.#iterateCharacters("[ref]: page.html\n\n[link][ref]");
105
+		this.#iterateCharacters("[link][ref]\n\n[ref]: page.html \"title\"");
106
+		this.#iterateCharacters("[link][ref]\n\n[ref]: page.html title");
107
+		this.#iterateCharacters("Lorem[^1] ipsum[^abc] dolor[^-1]\n\n[^abc]: def\n[^1]: def\n[^-1]: def\n[^abc]: def");
108
+		this.#iterateCharacters("Lorem[^1]\n\n[^2]: def");
109
+		this.#iterateCharacters("[^1]] [[^2] [^3]] [[^4]] ]^5[");
110
+		this.#iterateCharacters("[^1]: Def\n\nLorem[^1]");
111
+		this.#iterateCharacters("[^1]: Def\n\nLorem[^2]");
112
+		this.#iterateCharacters("Lorem <foo> ipsum");
113
+		this.#iterateCharacters("Lorem <span> ipsum");
114
+		this.#iterateCharacters("Lorem <span class=\"foo\" id=\"foo\" lang=\"en\"> ipsum");
115
+		this.#iterateCharacters("Lorem </span> ipsum");
116
+		this.#iterateCharacters("Lorem </span foo=\"bar\">");
117
+		this.#iterateCharacters("Lorem <span foo='bar' baz=ipsum dolor> sit");
118
+		this.#iterateCharacters("\\*\\\\*\\\\\\*\\\\\\\\*\\*");
119
+	}
120
+}

+ 17
- 7
php/markdown.php ファイルの表示

@@ -120,6 +120,15 @@ class MDUtils {
120 120
 		$tn = gettype($value);
121 121
 		return ($tn === 'object') ? get_class($value) : $tn;
122 122
 	}
123
+
124
+	/**
125
+	 * Returns the value if it's a string, otherwise returns an empty string.
126
+	 * For wrapping regex capture groups which may be `false` instead of a
127
+	 * string.
128
+	 */
129
+	public static function makeString(mixed $text, ?string $fallback=''): ?string {
130
+		return gettype($text) === 'string' && $text !== '' ? $text : $fallback;
131
+	}
123 132
 }
124 133
 
125 134
 /**
@@ -1481,7 +1490,7 @@ class MDTagModifier {
1481 1490
 			if (!$found) return [ $line, null ];
1482 1491
 		}
1483 1492
 		if (!mb_eregi(self::trailingClassRegex, $line, $groups)) return [ $line, null ];
1484
-		$bareLine = $groups[1];
1493
+		$bareLine = MDUtils::makeString($groups[1]);
1485 1494
 		$mod = self::fromContents($groups[2]);
1486 1495
 		return [ $bareLine, $mod ];
1487 1496
 	}
@@ -2029,7 +2038,7 @@ class MDFencedCodeBlockReader extends MDReader {
2029 2038
 		$openFenceLine = $state->lines[$p++];
2030 2039
 		[$openFenceLine, $modifier] = MDTagModifier::fromLine($openFenceLine, $state);
2031 2040
 		if (!mb_eregi('```\\s*([a-z0-9]*)\\s*$', $openFenceLine, $groups)) return null;
2032
-		$language = $groups[1] !== false && mb_strlen($groups[1]) > 0 ? $groups[1] : null;
2041
+		$language = MDUtils::makeString($groups[1], null);
2033 2042
 		$codeLines = [];
2034 2043
 		while ($state->hasLines(1, $p)) {
2035 2044
 			$line = $state->lines[$p++];
@@ -2255,7 +2264,8 @@ class MDDefinitionListReader extends MDReader {
2255 2264
 		if ($termCount == 0 || $definitionCount == 0) return null;
2256 2265
 		$blocks = array_map(function($line) use ($state) {
2257 2266
 			if (mb_eregi('^:\\s+(.*?)$', $line, $groups)) {
2258
-				return new MDDefinitionListDefinitionNode($state->inlineMarkdownToNodes($groups[1]));
2267
+				$content = MDUtils::makeString($groups[1]);
2268
+				return new MDDefinitionListDefinitionNode($state->inlineMarkdownToNodes($content));
2259 2269
 			} else {
2260 2270
 				return new MDDefinitionListTermNode($state->inlineMarkdownToNodes($line));
2261 2271
 			}
@@ -2315,8 +2325,8 @@ class MDFootnoteReader extends MDReader {
2315 2325
 	public function readBlock(MDState $state): ?MDBlockNode {
2316 2326
 		$p = $state->p;
2317 2327
 		if (!mb_eregi('^\\s*\\[\\^\\s*([^\\]]+)\\s*\\]:\\s+(.*)\\s*$', $state->lines[$p++], $groups)) return null;
2318
-		$symbol = $groups[1];
2319
-		$def = $groups[2];
2328
+		$symbol = MDUtils::makeString($groups[1]);
2329
+		$def = MDUtils::makeString($groups[2]);
2320 2330
 		while ($state->hasLines(1, $p)) {
2321 2331
 			$line = $state->lines[$p++];
2322 2332
 			if (mb_eregi('^\\s+', $line)) {
@@ -2414,8 +2424,8 @@ class MDAbbreviationReader extends MDReader {
2414 2424
 		$p = $state->p;
2415 2425
 		$line = $state->lines[$p++];
2416 2426
 		if (!mb_eregi('^\\s*\\*\\[([^\\]]+?)\\]:\\s+(.*?)\\s*$', $line, $groups)) return null;
2417
-		$abbrev = $groups[1];
2418
-		$def = $groups[2];
2427
+		$abbrev = MDUtils::makeString($groups[1]);
2428
+		$def = MDUtils::makeString($groups[2]);
2419 2429
 		$this->defineAbbreviation($state, $abbrev, $def);
2420 2430
 		$state->p = $p;
2421 2431
 		return new MDBlockNode(); // empty

+ 132
- 0
phptest/BrokenSyntaxTests.php ファイルの表示

@@ -0,0 +1,132 @@
1
+<?php
2
+declare(strict_types=1);
3
+
4
+use PHPUnit\Framework\TestCase;
5
+
6
+require_once __DIR__ . '/../php/markdown.php';
7
+
8
+final class BrokenSyntaxTests extends TestCase {
9
+	private ?Markdown $parser = null;
10
+
11
+	protected function setUp(): void {
12
+		parent::setUp();
13
+		$this->parser = Markdown::completeParser();
14
+	}
15
+
16
+	private function iterateCharacters(string $markdown) {
17
+		for ($i = 1; $i < mb_strlen($markdown); $i++) {
18
+			$portion = mb_substr($markdown, 0, $i);
19
+			try {
20
+				$this->parser->toHTML($portion);
21
+			} catch (Error $e) {
22
+				print("Broken portion is:\n\"{$portion}\"\n");
23
+				throw $e;
24
+			}
25
+		}
26
+	}
27
+
28
+	public function test_brokenSyntax() {
29
+		// Just try a bunch of broken syntax and make sure no exceptions are
30
+		// thrown or infinite loops triggered. Don't care about what's rendered
31
+		// as most of these are incomplete or invalid syntax.
32
+		$this->iterateCharacters("# ");
33
+		$this->iterateCharacters("## ");
34
+		$this->iterateCharacters("Underlined\n-");
35
+		$this->iterateCharacters("* ");
36
+		$this->iterateCharacters(" * A");
37
+		$this->iterateCharacters("- A");
38
+		$this->iterateCharacters(" - A");
39
+		$this->iterateCharacters("+ A");
40
+		$this->iterateCharacters(" + A");
41
+		$this->iterateCharacters("1. A");
42
+		$this->iterateCharacters(" 1. A");
43
+		$this->iterateCharacters("1. A\n 1. B\n 2. C\n2. D");
44
+		$this->iterateCharacters("> A");
45
+		$this->iterateCharacters("> \nA");
46
+		$this->iterateCharacters("> \n>");
47
+		$this->iterateCharacters(">>");
48
+		$this->iterateCharacters("> > A");
49
+		$this->iterateCharacters("> \n> A");
50
+		$this->iterateCharacters("> > \n> A");
51
+		$this->iterateCharacters("```\ncode\n```");
52
+		$this->iterateCharacters("```java\ncode\n```");
53
+		$this->iterateCharacters("    code\n    code\n\n");
54
+		$this->iterateCharacters("------");
55
+		$this->iterateCharacters("  -  -    - --*");
56
+		$this->iterateCharacters("Header|Header\n--|--\nCell|Cell");
57
+		$this->iterateCharacters("Header\n--|--\nCell");
58
+		$this->iterateCharacters("|Header|\n|-|\n|Cell|");
59
+		$this->iterateCharacters("|Header|\n|--|--|--|\n|Cell|");
60
+		$this->iterateCharacters("|Header|Header|\n|--|\n|Cell|Cell|");
61
+		$this->iterateCharacters("|Header|\n|:-|\n|Cell|");
62
+		$this->iterateCharacters("|Header|\n|:-:|\n|Cell|");
63
+		$this->iterateCharacters("|Header|\n|-:|\n|Cell|");
64
+		$this->iterateCharacters("term\n: definition\nterm\n: definition");
65
+		$this->iterateCharacters("term\n:definition\nterm\n:definition");
66
+		$this->iterateCharacters("HTML\n\n*[HTML]: Hypertext");
67
+		$this->iterateCharacters("HTML\n*[HTML]: Hypertext");
68
+		$this->iterateCharacters("*[HTML]: Hypertext\nHTML");
69
+		$this->iterateCharacters("---{.foo}\n");
70
+		$this->iterateCharacters("---{#foo}\n");
71
+		$this->iterateCharacters("---{.foo #foo lang=en}\n");
72
+		$this->iterateCharacters("lorem *ipsum* dolor *sit* amet");
73
+		$this->iterateCharacters("*lorem* ipsum *dolor* sit *amet*");
74
+		$this->iterateCharacters("*lorem *ipsum *dolor*** sit");
75
+		$this->iterateCharacters("***lorem* ipsum* dolor* sit");
76
+		$this->iterateCharacters("lorem _ipsum_ dolor _sit_ amet");
77
+		$this->iterateCharacters("_lorem_ ipsum _dolor_ sit _amet_");
78
+		$this->iterateCharacters("_lorem _ipsum _dolor___ sit");
79
+		$this->iterateCharacters("___lorem_ ipsum_ dolor_ sit");
80
+		$this->iterateCharacters("lorem **ipsum** dolor **sit** amet");
81
+		$this->iterateCharacters("**lorem** ipsum **dolor** sit **amet**");
82
+		$this->iterateCharacters("**lorem **ipsum **dolor****** sit");
83
+		$this->iterateCharacters("******lorem** ipsum** dolor** sit");
84
+		$this->iterateCharacters("lorem __ipsum__ dolor __sit__ amet");
85
+		$this->iterateCharacters("__lorem__ ipsum __dolor__ sit __amet__");
86
+		$this->iterateCharacters("lorem __ipsum__ dolor __sit__ amet");
87
+		$this->iterateCharacters("__lorem __ipsum __dolor______ sit");
88
+		$this->iterateCharacters("______lorem__ ipsum__ dolor__ sit");
89
+		$this->iterateCharacters("*_**`~~^==!__~*^!``**-+!**`==!~_^**``_");
90
+		$this->iterateCharacters("[link[(index.html(");
91
+		$this->iterateCharacters("[link[](index.html()");
92
+		$this->iterateCharacters("[link]](index.html))");
93
+		$this->iterateCharacters("[[link]((index.html)");
94
+		$this->iterateCharacters("]link])index.html)");
95
+		$this->iterateCharacters("[ link ] ( index.html )");
96
+		$this->iterateCharacters("(user@example.com)");
97
+		$this->iterateCharacters("(https://user@example.com)");
98
+		$this->iterateCharacters("(index.html \"title\")");
99
+		$this->iterateCharacters("(index.html \"title)");
100
+		$this->iterateCharacters("(index.html title\")");
101
+		$this->iterateCharacters("(index.html title)");
102
+		$this->iterateCharacters("![alt][image.jpg]");
103
+		$this->iterateCharacters("![alt[](image.jpg()");
104
+		$this->iterateCharacters("! [alt](image.jpg)");
105
+		$this->iterateCharacters("!(image.jpg)");
106
+		$this->iterateCharacters("[][]]][][[][][][[][[]][][][][][[[]]]][]][][");
107
+		$this->iterateCharacters("())())()()()(())(()((())))(()((()");
108
+		$this->iterateCharacters("<https://example.com>");
109
+		$this->iterateCharacters("<<https://example.com>");
110
+		$this->iterateCharacters("<https://example.com>>");
111
+		$this->iterateCharacters("[link][ref]\n\n[ref]: page.html");
112
+		$this->iterateCharacters("[link][ref]\n[ref]: page.html");
113
+		$this->iterateCharacters("[link][ref]\n\n[ref]:page.html");
114
+		$this->iterateCharacters("[ref]: page.html\n\n[link][ref]");
115
+		$this->iterateCharacters("[link][ref]\n\n[ref]: page.html \"title\"");
116
+		$this->iterateCharacters("[link][ref]\n\n[ref]: page.html title");
117
+		$this->iterateCharacters("Lorem[^1] ipsum[^abc] dolor[^-1]\n\n[^abc]: def\n[^1]: def\n[^-1]: def\n[^abc]: def");
118
+		$this->iterateCharacters("Lorem[^1]\n\n[^2]: def");
119
+		$this->iterateCharacters("[^1]] [[^2] [^3]] [[^4]] ]^5[");
120
+		$this->iterateCharacters("[^1]: Def\n\nLorem[^1]");
121
+		$this->iterateCharacters("[^1]: Def\n\nLorem[^2]");
122
+		$this->iterateCharacters("Lorem <foo> ipsum");
123
+		$this->iterateCharacters("Lorem <span> ipsum");
124
+		$this->iterateCharacters("Lorem <span class=\"foo\" id=\"foo\" lang=\"en\"> ipsum");
125
+		$this->iterateCharacters("Lorem </span> ipsum");
126
+		$this->iterateCharacters("Lorem </span foo=\"bar\">");
127
+		$this->iterateCharacters("Lorem <span foo='bar' baz=ipsum dolor> sit");
128
+		$this->iterateCharacters("\\*\\\\*\\\\\\*\\\\\\\\*\\*");
129
+		$this->assertTrue(true); // to suppress risky test warning
130
+	}
131
+}
132
+?>

+ 1
- 0
runphptests.sh ファイルの表示

@@ -4,6 +4,7 @@ php lib/phpunit.phar --display-warnings --display-notices \
4 4
 	phptest/TokenTests.php \
5 5
 	phptest/InlineTests.php \
6 6
 	phptest/BlockTests.php \
7
+	phptest/BrokenSyntaxTests.php \
7 8
 	phptest/spreadsheet/CellAddressRangeTests.php \
8 9
 	phptest/spreadsheet/CellValueTests.php \
9 10
 	phptest/spreadsheet/ExpressionSetTests.php \

+ 2
- 0
testjs.html ファイルの表示

@@ -442,6 +442,7 @@
442 442
 		<script src="jstest/UtilsTests.js"></script>
443 443
 		<script src="jstest/InlineTests.js"></script>
444 444
 		<script src="jstest/BlockTests.js"></script>
445
+		<script src="jstest/BrokenSyntaxTests.js"></script>
445 446
 		<script src="jstest/spreadsheet/CellValueTests.js"></script>
446 447
 		<script src="jstest/spreadsheet/CellAddressRangeTests.js"></script>
447 448
 		<script src="jstest/spreadsheet/ExpressionSetTests.js"></script>
@@ -453,6 +454,7 @@
453 454
 					UtilsTests,
454 455
 					InlineTests,
455 456
 					BlockTests,
457
+					BrokenSyntaxTests,
456 458
 					CellValueTests,
457 459
 					CellAddressRangeTests,
458 460
 					ExpressionSetTests,

読み込み中…
キャンセル
保存