| @@ -1,6 +1,9 @@ |
| 1 | 1 | package texmath |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | "encoding/xml" |
| 5 | "errors" |
| 6 | "io" |
| 4 | 7 | "regexp" |
| 5 | 8 | "strings" |
| 6 | 9 | "testing" |
| @@ -78,6 +81,8 @@ func TestConvertRefuses(t *testing.T) { |
| 78 | 81 | `\style{color:red}{x}`, `\color{red}{x}`, `\class{a}{x}`, `\htmlId{a}{x}`, |
| 79 | 82 | `\def\a{x}\a`, `\newcommand{\a}{x}`, `\require{html}`, `\unicode{x}`, |
| 80 | 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 | 87 | if out, err := Convert(tex, false); err == nil { |
| 83 | 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 | } |