| @@ -1,6 +1,9 @@ |
| 1 | package texmath |
1 | package texmath |
| 2 | |
2 | |
| 3 | import ( |
3 | import ( |
| |
4 | "encoding/xml" |
| |
5 | "errors" |
| |
6 | "io" |
| 4 | "regexp" |
7 | "regexp" |
| 5 | "strings" |
8 | "strings" |
| 6 | "testing" |
9 | "testing" |
| @@ -78,6 +81,8 @@ func TestConvertRefuses(t *testing.T) { |
| 78 | `\style{color:red}{x}`, `\color{red}{x}`, `\class{a}{x}`, `\htmlId{a}{x}`, |
81 | `\style{color:red}{x}`, `\color{red}{x}`, `\class{a}{x}`, `\htmlId{a}{x}`, |
| 79 | `\def\a{x}\a`, `\newcommand{\a}{x}`, `\require{html}`, `\unicode{x}`, |
82 | `\def\a{x}\a`, `\newcommand{\a}{x}`, `\require{html}`, `\unicode{x}`, |
| 80 | `\includegraphics{x}`, `\input{/etc/passwd}`, |
83 | `\includegraphics{x}`, `\input{/etc/passwd}`, |
| |
84 | `\sqrt\displaystyle`, `\frac\displaystyle y`, `\hat\displaystyle`, |
| |
85 | `\overbrace\displaystyle`, `\binom\displaystyle1`, `\mathbf\limits`, `x^\nolimits`, |
| 81 | } { |
86 | } { |
| 82 | if out, err := Convert(tex, false); err == nil { |
87 | if out, err := Convert(tex, false); err == nil { |
| 83 | t.Errorf("Convert(%q) = %s, want an error", tex, out) |
88 | t.Errorf("Convert(%q) = %s, want an error", tex, out) |
| @@ -167,3 +172,53 @@ func TestConvertEmitsOnlyListed(t *testing.T) { |
| 167 | } |
172 | } |
| 168 | } |
173 | } |
| 169 | } |
174 | } |
| |
175 | |
| |
176 | // FuzzConvert: no panic, output bounded by the input, and output that is |
| |
177 | // well-formed XML using only the listed elements and attribute values. |
| |
178 | func FuzzConvert(f *testing.F) { |
| |
179 | for _, seed := range []string{ |
| |
180 | `x^2`, `\frac{a}{b}`, `\sqrt[3]{x}`, `\left(\frac12\right)`, `\sum_{i=1}^n i`, |
| |
181 | `\begin{pmatrix}a&b\\c&d\end{pmatrix}`, `\text{a<b}`, `\mathbb{R}`, `\hat{x}~'`, |
| |
182 | `\sqrt\displaystyle`, `\operatorname*{argmax}_x`, `{{{x}}}`, `a\,b\quad c`, |
| |
183 | } { |
| |
184 | f.Add(seed, false) |
| |
185 | } |
| |
186 | allowed := map[string]bool{} |
| |
187 | for _, e := range Elements { |
| |
188 | allowed[e] = true |
| |
189 | } |
| |
190 | length := regexp.MustCompile(`^-?[0-9]+(\.[0-9]+)?em$`) |
| |
191 | f.Fuzz(func(t *testing.T, tex string, display bool) { |
| |
192 | out, err := Convert(tex, display) |
| |
193 | if err != nil { |
| |
194 | return |
| |
195 | } |
| |
196 | if len(out) > 64*len(tex)+256 { |
| |
197 | t.Fatalf("%d bytes out for %d in", len(out), len(tex)) |
| |
198 | } |
| |
199 | d := xml.NewDecoder(strings.NewReader(out)) |
| |
200 | for { |
| |
201 | tok, err := d.Token() |
| |
202 | if errors.Is(err, io.EOF) { |
| |
203 | break |
| |
204 | } |
| |
205 | if err != nil { |
| |
206 | t.Fatalf("not XML: %v\n%s", err, out) |
| |
207 | } |
| |
208 | start, ok := tok.(xml.StartElement) |
| |
209 | if !ok { |
| |
210 | continue |
| |
211 | } |
| |
212 | if !allowed[start.Name.Local] || start.Name.Space != "" { |
| |
213 | t.Fatalf("element %v in %s", start.Name, out) |
| |
214 | } |
| |
215 | for _, a := range start.Attr { |
| |
216 | want, ok := Attrs[start.Name.Local][a.Name.Local] |
| |
217 | if !ok || a.Name.Space != "" || (want == "<length>" && !length.MatchString(a.Value)) || |
| |
218 | (want != "<length>" && want != a.Value) { |
| |
219 | t.Fatalf("attribute %v=%q on %s in %s", a.Name, a.Value, start.Name.Local, out) |
| |
220 | } |
| |
221 | } |
| |
222 | } |
| |
223 | }) |
| |
224 | } |