Skip to content

Commit 5eb064f

Browse files
committed
#482 #483 #491 Basic fuzz testing of the line breaking algorithms.
This should ensure no infinite loop bugs creep in over time.
1 parent 990012c commit 5eb064f

File tree

1 file changed

+141
-1
lines changed

1 file changed

+141
-1
lines changed

openhtmltopdf-examples/src/test/java/com/openhtmltopdf/nonvisualregressiontests/NonVisualRegressionTest.java

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
import java.io.File;
1010
import java.io.IOException;
1111
import java.util.ArrayList;
12+
import java.util.Arrays;
1213
import java.util.List;
14+
import java.util.Locale;
15+
import java.util.Random;
1316

1417
import org.apache.commons.io.FileUtils;
1518
import org.apache.pdfbox.io.IOUtils;
@@ -29,11 +32,11 @@
2932
import org.apache.pdfbox.util.Charsets;
3033
import org.hamcrest.CustomTypeSafeMatcher;
3134
import org.junit.Assert;
32-
import org.junit.Ignore;
3335
import org.junit.Test;
3436

3537
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder;
3638
import com.openhtmltopdf.testcases.TestcaseRunner;
39+
import com.openhtmltopdf.visualregressiontests.VisualRegressionTest;
3740
import com.openhtmltopdf.visualtest.TestSupport;
3841
import com.openhtmltopdf.visualtest.VisualTester.BuilderConfig;
3942

@@ -821,6 +824,143 @@ public void testPR480LinkShapes() throws IOException {
821824
}
822825
}
823826

827+
/**
828+
* Runs a fuzz test, optionally with PDFBOX included font with non-zero-width
829+
* soft hyphen.
830+
*/
831+
private static void runFuzzTest(String html, boolean useFont) throws IOException {
832+
final String header = useFont ?
833+
"<html><body style=\"font-family: 'Liberation Sans'\">" :
834+
"<html><body>";
835+
final String footer = "</body></html>";
836+
837+
System.out.println("The test is " + html.length() + " chars long.");
838+
839+
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
840+
PdfRendererBuilder builder = new PdfRendererBuilder();
841+
builder.useFastMode();
842+
builder.toStream(os);
843+
builder.withHtmlContent(header + html + footer, null);
844+
if (useFont) {
845+
builder.useFont(() -> VisualRegressionTest.class.getClassLoader().getResourceAsStream("org/apache/pdfbox/resources/ttf/LiberationSans-Regular.ttf"),
846+
"Liberation Sans");
847+
}
848+
builder.run();
849+
850+
// Files.write(Paths.get("./target/html.txt"), html.getBytes(StandardCharsets.UTF_8));
851+
// Files.write(Paths.get("./target/pdf.pdf"), os.toByteArray());
852+
853+
System.out.println("The result is " + os.size() + " bytes long.");
854+
}
855+
}
856+
857+
/**
858+
* Creates a fuzz test with random characters given the styles provided in
859+
* arguments.
860+
*/
861+
private static void createCombinationTest(
862+
StringBuilder sb, int widthPx, String whiteSpace, String wordWrap, List<char[]> all, Random rndm, int testCharCount) {
863+
String start = String.format(Locale.US, "<div style=\"white-space: %s; word-wrap: %s; width: %dpx;\">",
864+
whiteSpace, wordWrap, widthPx);
865+
String end = "</div>";
866+
867+
sb.append(start);
868+
869+
int len = 0;
870+
while (true) {
871+
char[] charCombi = all.get(rndm.nextInt(all.size()));
872+
873+
if (len + charCombi.length > testCharCount) {
874+
String combi = String.valueOf(charCombi);
875+
sb.append(combi.substring(0, testCharCount - len));
876+
break;
877+
}
878+
879+
sb.append(charCombi);
880+
len += charCombi.length;
881+
}
882+
883+
sb.append(end);
884+
}
885+
886+
/**
887+
* Creates all 5 character combinations from a list of characters
888+
* which have special meaning to the line breaking algorithms.
889+
*/
890+
private static List<char[]> createAllCombinations() {
891+
char[] chars = new char[] { 'x', '\u00ad', '\n', '\r', ' ' };
892+
int[] loopIndices = new int[chars.length];
893+
int totalCombinations = (int) Math.pow(loopIndices.length, loopIndices.length);
894+
895+
List<char[]> ret = new ArrayList<>(totalCombinations);
896+
897+
for (int i = 0; i < totalCombinations; i++) {
898+
char[] result = new char[loopIndices.length];
899+
900+
for (int k = 0; k < loopIndices.length; k++) {
901+
char ch = chars[loopIndices[k]];
902+
result[k] = ch;
903+
}
904+
905+
ret.add(result);
906+
907+
boolean carry = true;
908+
for (int j = loopIndices.length - 1; j >= 0; j--) {
909+
if (carry) {
910+
loopIndices[j]++;
911+
carry = false;
912+
}
913+
914+
if (loopIndices[j] >= chars.length) {
915+
loopIndices[j] = 0;
916+
carry = true;
917+
}
918+
}
919+
}
920+
921+
return ret;
922+
}
923+
924+
/**
925+
* Tests the line breaking algorithms against infinite loop bugs
926+
* by using many combinations of styles and random character sequences.
927+
*/
928+
@Test
929+
public void testPr492InfiniteLoopBugsInLineBreakingFuzz() throws IOException {
930+
final String[] whiteSpace = new String[] { "normal", "pre", "nowrap", "pre-wrap", "pre-line" };
931+
final String[] wordWrap = new String[] { "normal", "break-word" };
932+
final List<char[]> all = createAllCombinations();
933+
final Random rndm = new Random();
934+
long seed = rndm.nextLong();
935+
936+
System.out.println("For NonVisualRegressionTest::testPr492InfiniteLoopBugsInLineBreakingFuzz " +
937+
"using a random seed of " + seed + " for Random instance.");
938+
rndm.setSeed(seed);
939+
940+
List<Integer> lengths = new ArrayList<>();
941+
lengths.addAll(Arrays.asList(0, 1, 2, 3, 37, 79));
942+
943+
for (int i = 0; i < 4; i++) {
944+
lengths.add(rndm.nextInt(150));
945+
}
946+
947+
StringBuilder sb = new StringBuilder();
948+
949+
for (int i = 0; i < 100; i++) {
950+
for (int j = 0; j < whiteSpace.length; j++) {
951+
for (int k = 0; k < wordWrap.length; k++) {
952+
for (Integer len : lengths) {
953+
createCombinationTest(sb, i, whiteSpace[j], wordWrap[k], all, rndm, len);
954+
}
955+
}
956+
}
957+
}
958+
959+
runFuzzTest(sb.toString(), false);
960+
runFuzzTest(sb.toString(), true);
961+
}
962+
963+
824964
// TODO:
825965
// + More form controls.
826966
// + Custom meta info.

0 commit comments

Comments
 (0)