1//
2// Blackfriday Markdown Processor
3// Available at http://github.com/russross/blackfriday
4//
5// Copyright © 2011 Russ Ross <russ@russross.com>.
6// Distributed under the Simplified BSD License.
7// See README.md for details.
8//
9
10//
11// Helper functions for unit testing
12//
13
14package blackfriday
15
16import (
17	"io/ioutil"
18	"path/filepath"
19	"regexp"
20	"testing"
21
22	"github.com/pmezard/go-difflib/difflib"
23)
24
25type TestParams struct {
26	extensions        Extensions
27	referenceOverride ReferenceOverrideFunc
28	HTMLFlags
29	HTMLRendererParameters
30}
31
32func execRecoverableTestSuite(t *testing.T, tests []string, params TestParams, suite func(candidate *string)) {
33	// Catch and report panics. This is useful when running 'go test -v' on
34	// the integration server. When developing, though, crash dump is often
35	// preferable, so recovery can be easily turned off with doRecover = false.
36	var candidate string
37	const doRecover = true
38	if doRecover {
39		defer func() {
40			if err := recover(); err != nil {
41				t.Errorf("\npanic while processing [%#v]: %s\n", candidate, err)
42			}
43		}()
44	}
45	suite(&candidate)
46}
47
48func runMarkdown(input string, params TestParams) string {
49	params.HTMLRendererParameters.Flags = params.HTMLFlags
50	renderer := NewHTMLRenderer(params.HTMLRendererParameters)
51	return string(Run([]byte(input), WithRenderer(renderer),
52		WithExtensions(params.extensions),
53		WithRefOverride(params.referenceOverride)))
54}
55
56// doTests runs full document tests using MarkdownCommon configuration.
57func doTests(t *testing.T, tests []string) {
58	doTestsParam(t, tests, TestParams{
59		extensions: CommonExtensions,
60		HTMLRendererParameters: HTMLRendererParameters{
61			Flags: CommonHTMLFlags,
62		},
63	})
64}
65
66func doTestsBlock(t *testing.T, tests []string, extensions Extensions) {
67	doTestsParam(t, tests, TestParams{
68		extensions: extensions,
69		HTMLFlags:  UseXHTML,
70	})
71}
72
73func doTestsParam(t *testing.T, tests []string, params TestParams) {
74	execRecoverableTestSuite(t, tests, params, func(candidate *string) {
75		for i := 0; i+1 < len(tests); i += 2 {
76			input := tests[i]
77			*candidate = input
78			expected := tests[i+1]
79			actual := runMarkdown(*candidate, params)
80			if actual != expected {
81				t.Errorf("\nInput   [%#v]\nExpected[%#v]\nActual  [%#v]",
82					*candidate, expected, actual)
83			}
84
85			// now test every substring to stress test bounds checking
86			if !testing.Short() {
87				for start := 0; start < len(input); start++ {
88					for end := start + 1; end <= len(input); end++ {
89						*candidate = input[start:end]
90						runMarkdown(*candidate, params)
91					}
92				}
93			}
94		}
95	})
96}
97
98func doTestsInline(t *testing.T, tests []string) {
99	doTestsInlineParam(t, tests, TestParams{})
100}
101
102func doLinkTestsInline(t *testing.T, tests []string) {
103	doTestsInline(t, tests)
104
105	prefix := "http://localhost"
106	params := HTMLRendererParameters{AbsolutePrefix: prefix}
107	transformTests := transformLinks(tests, prefix)
108	doTestsInlineParam(t, transformTests, TestParams{
109		HTMLRendererParameters: params,
110	})
111	doTestsInlineParam(t, transformTests, TestParams{
112		HTMLFlags:              UseXHTML,
113		HTMLRendererParameters: params,
114	})
115}
116
117func doSafeTestsInline(t *testing.T, tests []string) {
118	doTestsInlineParam(t, tests, TestParams{HTMLFlags: Safelink})
119
120	// All the links in this test should not have the prefix appended, so
121	// just rerun it with different parameters and the same expectations.
122	prefix := "http://localhost"
123	params := HTMLRendererParameters{AbsolutePrefix: prefix}
124	transformTests := transformLinks(tests, prefix)
125	doTestsInlineParam(t, transformTests, TestParams{
126		HTMLFlags:              Safelink,
127		HTMLRendererParameters: params,
128	})
129}
130
131func doTestsInlineParam(t *testing.T, tests []string, params TestParams) {
132	params.extensions |= Autolink | Strikethrough
133	params.HTMLFlags |= UseXHTML
134	doTestsParam(t, tests, params)
135}
136
137func transformLinks(tests []string, prefix string) []string {
138	newTests := make([]string, len(tests))
139	anchorRe := regexp.MustCompile(`<a href="/(.*?)"`)
140	imgRe := regexp.MustCompile(`<img src="/(.*?)"`)
141	for i, test := range tests {
142		if i%2 == 1 {
143			test = anchorRe.ReplaceAllString(test, `<a href="`+prefix+`/$1"`)
144			test = imgRe.ReplaceAllString(test, `<img src="`+prefix+`/$1"`)
145		}
146		newTests[i] = test
147	}
148	return newTests
149}
150
151func doTestsReference(t *testing.T, files []string, flag Extensions) {
152	params := TestParams{extensions: flag}
153	execRecoverableTestSuite(t, files, params, func(candidate *string) {
154		for _, basename := range files {
155			filename := filepath.Join("testdata", basename+".text")
156			inputBytes, err := ioutil.ReadFile(filename)
157			if err != nil {
158				t.Errorf("Couldn't open '%s', error: %v\n", filename, err)
159				continue
160			}
161			input := string(inputBytes)
162
163			filename = filepath.Join("testdata", basename+".html")
164			expectedBytes, err := ioutil.ReadFile(filename)
165			if err != nil {
166				t.Errorf("Couldn't open '%s', error: %v\n", filename, err)
167				continue
168			}
169			expected := string(expectedBytes)
170
171			actual := string(runMarkdown(input, params))
172			if actual != expected {
173				t.Errorf("\n" + doTestDiff(basename, expected, actual))
174			}
175
176			// now test every prefix of every input to check for
177			// bounds checking
178			if !testing.Short() {
179				start, max := 0, len(input)
180				for end := start + 1; end <= max; end++ {
181					*candidate = input[start:end]
182					runMarkdown(*candidate, params)
183				}
184			}
185		}
186	})
187}
188
189func doTestDiff(name, expected, actual string) string {
190	d, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
191		A:        difflib.SplitLines(expected),
192		B:        difflib.SplitLines(actual),
193		FromFile: "expect: " + name,
194		ToFile:   "actual: " + name,
195		Context:  1,
196	})
197	return d
198}
199