From 9c0408ace4264416f7182a128e80ef1b386297be Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20=E2=80=98Bombe=E2=80=99=20Roden?= Date: Fri, 8 Nov 2013 22:36:40 +0100 Subject: [PATCH] Rewrite parser tests. --- .../sone/text/SoneTextParserTest.java | 404 ++++++++++++++------- 1 file changed, 267 insertions(+), 137 deletions(-) diff --git a/src/test/java/net/pterodactylus/sone/text/SoneTextParserTest.java b/src/test/java/net/pterodactylus/sone/text/SoneTextParserTest.java index 34767b5..b67fea8 100644 --- a/src/test/java/net/pterodactylus/sone/text/SoneTextParserTest.java +++ b/src/test/java/net/pterodactylus/sone/text/SoneTextParserTest.java @@ -17,157 +17,287 @@ package net.pterodactylus.sone.text; +import static com.google.common.collect.ImmutableList.builder; +import static java.util.Arrays.asList; +import static java.util.UUID.randomUUID; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + import java.io.IOException; import java.io.StringReader; -import java.util.Arrays; +import java.util.Collection; -import net.pterodactylus.sone.database.memory.MemoryDatabase; +import net.pterodactylus.sone.data.Mocks; +import net.pterodactylus.sone.data.Post; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.data.impl.DefaultSone; -import junit.framework.TestCase; +import com.google.common.collect.ImmutableList; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; +import org.hamcrest.collection.IsIterableContainingInOrder; +import org.junit.Test; /** * JUnit test case for {@link SoneTextParser}. * * @author David ‘Bombe’ Roden */ -public class SoneTextParserTest extends TestCase { - - // - // ACTIONS - // - - /** - * Tests basic plain-text operation of the parser. - * - * @throws IOException - * if an I/O error occurs - */ - @SuppressWarnings("static-method") - public void testPlainText() throws IOException { - SoneTextParser soneTextParser = new SoneTextParser(new MemoryDatabase(null)); - Iterable parts; - - /* check basic operation. */ - parts = soneTextParser.parse(null, new StringReader("Test.")); - assertNotNull("Parts", parts); - assertEquals("Part Text", "Test.", convertText(parts, PlainTextPart.class)); - - /* check empty lines at start and end. */ - parts = soneTextParser.parse(null, new StringReader("\nTest.\n\n")); - assertNotNull("Parts", parts); - assertEquals("Part Text", "Test.", convertText(parts, PlainTextPart.class)); - - /* check duplicate empty lines in the text. */ - parts = soneTextParser.parse(null, new StringReader("\nTest.\n\n\nTest.")); - assertNotNull("Parts", parts); - assertEquals("Part Text", "Test.\n\nTest.", convertText(parts, PlainTextPart.class)); - } - - /** - * Tests parsing of KSK links. - * - * @throws IOException - * if an I/O error occurs - */ - @SuppressWarnings("static-method") - public void testKSKLinks() throws IOException { - SoneTextParser soneTextParser = new SoneTextParser(new MemoryDatabase(null)); - Iterable parts; - - /* check basic links. */ - parts = soneTextParser.parse(null, new StringReader("KSK@gpl.txt")); - assertNotNull("Parts", parts); - assertEquals("Part Text", "[KSK@gpl.txt|gpl.txt|gpl.txt]", convertText(parts, FreenetLinkPart.class)); - - /* check embedded links. */ - parts = soneTextParser.parse(null, new StringReader("Link is KSK@gpl.txt\u200b.")); - assertNotNull("Parts", parts); - assertEquals("Part Text", "Link is [KSK@gpl.txt|gpl.txt|gpl.txt]\u200b.", convertText(parts, PlainTextPart.class, FreenetLinkPart.class)); - - /* check embedded links and line breaks. */ - parts = soneTextParser.parse(null, new StringReader("Link is KSK@gpl.txt\nKSK@test.dat\n")); - assertNotNull("Parts", parts); - assertEquals("Part Text", "Link is [KSK@gpl.txt|gpl.txt|gpl.txt]\n[KSK@test.dat|test.dat|test.dat]", convertText(parts, PlainTextPart.class, FreenetLinkPart.class)); - } - - /** - * Test case for a bug that was discovered in 0.6.7. - * - * @throws IOException - * if an I/O error occurs - */ - @SuppressWarnings({ "synthetic-access", "static-method" }) - public void testEmptyLinesAndSoneLinks() throws IOException { - SoneTextParser soneTextParser = new SoneTextParser(new MemoryDatabase(null)); - Iterable parts; - - /* check basic links. */ - parts = soneTextParser.parse(null, new StringReader("Some text.\n\nLink to sone://DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU and stuff.")); - assertNotNull("Parts", parts); - assertEquals("Part Text", "Some text.\n\nLink to [Sone|DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU] and stuff.", convertText(parts, PlainTextPart.class, SonePart.class)); - } - - /** - * Test for a bug discovered in Sone 0.8.4 where a plain “http://” would be - * parsed into a link. - * - * @throws IOException - * if an I/O error occurs - */ - @SuppressWarnings({ "synthetic-access", "static-method" }) - public void testEmpyHttpLinks() throws IOException { - SoneTextParser soneTextParser = new SoneTextParser(new MemoryDatabase(null)); - Iterable parts; - - /* check empty http links. */ - parts = soneTextParser.parse(null, new StringReader("Some text. Empty link: http:// – nice!")); - assertNotNull("Parts", parts); - assertEquals("Part Text", "Some text. Empty link: http:// – nice!", convertText(parts, PlainTextPart.class)); - } - - // - // PRIVATE METHODS - // - - /** - * Converts all given {@link Part}s into a string, validating that the - * part’s classes match only the expected classes. - * - * @param parts - * The parts to convert to text - * @param validClasses - * The valid classes; if no classes are given, all classes are - * valid - * @return The converted text - */ - private static String convertText(Iterable parts, Class... validClasses) { - StringBuilder text = new StringBuilder(); - for (Part part : parts) { - assertNotNull("Part", part); - boolean classValid = validClasses.length == 0; - for (Class validClass : validClasses) { - if (validClass.isAssignableFrom(part.getClass())) { - classValid = true; - break; +public class SoneTextParserTest { + + private final Mocks mocks = new Mocks(); + private final SoneTextParser soneTextParser = new SoneTextParser(mocks.database); + + private Matcher> matches(final Matcher... partsToMatch) { + return new TypeSafeMatcher>() { + + private Matcher> iterableMatcher; + + @Override + protected boolean matchesSafely(Iterable parts) { + iterableMatcher = new IsIterableContainingInOrder(asList(partsToMatch)); + return iterableMatcher.matches(collapseParts(expandParts(parts))); + } + + private Iterable expandParts(Iterable parts) { + PartContainer partContainer = new PartContainer(); + for (Part part : parts) { + partContainer.add(part); + } + return partContainer; + } + + private Collection collapseParts(Iterable parts) { + ImmutableList.Builder collapsedPartsBuilder = builder(); + PlainTextPart lastPlainTextPart = null; + for (Part part : parts) { + if (part instanceof PlainTextPart) { + if (lastPlainTextPart != null) { + lastPlainTextPart = new PlainTextPart(lastPlainTextPart.getText() + ((PlainTextPart) part).getText()); + } else { + lastPlainTextPart = (PlainTextPart) part; + } + } else { + if (lastPlainTextPart != null) { + collapsedPartsBuilder.add(lastPlainTextPart); + lastPlainTextPart = null; + } + collapsedPartsBuilder.add(part); + } + } + if (lastPlainTextPart != null) { + collapsedPartsBuilder.add(lastPlainTextPart); } + return collapsedPartsBuilder.build(); } - if (!classValid) { - fail("Part’s Class (" + part.getClass() + ") is not one of " + Arrays.toString(validClasses)); + + @Override + protected void describeMismatchSafely(Iterable parts, Description mismatchDescription) { + iterableMatcher.describeMismatch(collapseParts(parts), mismatchDescription); } - if (part instanceof PlainTextPart) { - text.append(((PlainTextPart) part).getText()); - } else if (part instanceof FreenetLinkPart) { - FreenetLinkPart freenetLinkPart = (FreenetLinkPart) part; - text.append('[').append(freenetLinkPart.getLink()).append('|').append(freenetLinkPart.isTrusted() ? "trusted|" : "").append(freenetLinkPart.getTitle()).append('|').append(freenetLinkPart.getText()).append(']'); - } else if (part instanceof LinkPart) { - LinkPart linkPart = (LinkPart) part; - text.append('[').append(linkPart.getLink()).append('|').append(linkPart.getTitle()).append('|').append(linkPart.getText()).append(']'); - } else if (part instanceof SonePart) { - SonePart sonePart = (SonePart) part; - text.append("[Sone|").append(sonePart.getSone().getId()).append(']'); + + @Override + public void describeTo(Description description) { + iterableMatcher.describeTo(description); } - } - return text.toString(); + }; + } + + private Iterable parse(String text) throws IOException { + return soneTextParser.parse(null, new StringReader(text)); + } + + private Iterable parse(SoneTextParserContext context, String text) throws IOException { + return soneTextParser.parse(context, new StringReader(text)); + } + + @Test + public void parsePlainText() throws IOException { + assertThat(parse("Test."), matches(is(new PlainTextPart("Test.")))); + } + + @Test + public void parsePlainTextWithEmptyLinesAtTheBeginningAndEnd() throws IOException { + assertThat(parse("\nTest.\n\n"), matches(is(new PlainTextPart("Test.")))); + } + + @Test + public void parsePlainTextAndCollapseMultipleEmptyLines() throws IOException { + assertThat(parse("\nTest.\n\n\nTest."), matches(is(new PlainTextPart("Test.\n\nTest.")))); + } + + @Test + public void parseSimpleKskLinks() throws IOException { + assertThat(parse("KSK@gpl.txt"), matches(is(new FreenetLinkPart("KSK@gpl.txt", "gpl.txt", false)))); + } + + @Test + public void parseEmbeddedLinks() throws IOException { + assertThat(parse("Link is KSK@gpl.txt\u200b."), matches( + is(new PlainTextPart("Link is ")), + is(new FreenetLinkPart("KSK@gpl.txt", "gpl.txt", false)), + is(new PlainTextPart("\u200b.")) + )); + } + + @Test + public void parseEmbeddedLinksAndLineBreaks() throws IOException { + assertThat(parse("Link is KSK@gpl.txt\nKSK@test.dat\n"), matches( + is(new PlainTextPart("Link is ")), + is(new FreenetLinkPart("KSK@gpl.txt", "gpl.txt", false)), + is(new PlainTextPart("\n")), + is(new FreenetLinkPart("KSK@test.dat", "test.dat", false)) + )); + } + + @Test + public void parseEmptyLinesAndSoneLinks() throws IOException { + Sone sone = mocks.mockSone("DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU").create(); + assertThat(parse("Some text.\n\nLink to sone://DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU and stuff."), matches( + is(new PlainTextPart("Some text.\n\nLink to ")), + is(new SonePart(sone)), + is(new PlainTextPart(" and stuff.")) + )); + } + + @Test + public void parseEmptyHttpLinks() throws IOException { + assertThat(parse("Some text. Empty link: http:// – nice!"), matches( + is(new PlainTextPart("Some text. Empty link: http:// – nice!")) + )); + } + + @Test + public void parseTrustedSoneLinks() throws IOException { + Sone trustedSone = mocks.mockSone("DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU").create(); + assertThat(parse(new SoneTextParserContext(trustedSone), "Get SSK@DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU/file.txt\u200b!"), matches( + is(new PlainTextPart("Get ")), + is(new FreenetLinkPart("SSK@DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU/file.txt", "file.txt", true)), + is(new PlainTextPart("\u200b!")) + )); + } + + @Test + public void parseHttpLink() throws IOException { + assertThat(parse("http://w3.org/foo.html"), matches( + is(new LinkPart("http://w3.org/foo.html", "w3.org/foo.html", "w3.org/foo.html")) + )); + } + + @Test + public void twoNonEmptyLinesAreParsedCorrectly() throws IOException { + assertThat(parse("First line.\nSecond line."), matches( + is(new PlainTextPart("First line.\nSecond line.")) + )); + } + + @Test + public void malformedChkLinkIsParsedAsText() throws IOException { + assertThat(parse("CHK@key/gpl.txt"), matches( + is(new PlainTextPart("CHK@key/gpl.txt")) + )); + } + + @Test + public void malformedUskLinkIsParsedAsText() throws IOException { + assertThat(parse("USK@key/site/"), matches( + is(new PlainTextPart("USK@key/site/")) + )); + } + + @Test + public void httpsLinksAreParsedCorrectly() throws IOException { + assertThat(parse("https://site/file.txt"), matches( + is(new LinkPart("https://site/file.txt", "site/file.txt")) + )); + } + + @Test + public void postLinksAreParsedCorrectly() throws IOException { + Sone sone = mocks.mockSone("Sone").create(); + Post post = mocks.mockPost(sone, randomUUID().toString()).create(); + assertThat(parse("post://" + post.getId()), matches( + is(new PostPart(post)) + )); + } + + @Test + public void linkToNonExistingPostIsParsedAsPlainText() throws IOException { + String postId = randomUUID().toString(); + assertThat(parse("post://" + postId), matches( + is(new PlainTextPart("post://" + postId)) + )); + } + + @Test + public void tooShortPostLinkIsParsedAsPlainText() throws IOException { + assertThat(parse("post://post"), matches( + is(new PlainTextPart("post://post")) + )); + } + + @Test + public void freenetPrefixBeforeKeysIsCutOff() throws IOException { + assertThat(parse("freenet:KSK@gpl.txt"), matches( + is(new FreenetLinkPart("KSK@gpl.txt", "gpl.txt", false)) + )); + } + + @Test + public void linkToNonExistingSoneCreatesLinkToEmptyShell() throws IOException { + assertThat(parse("sone://1234567890123456789012345678901234567890123"), matches( + is(new SonePart(new DefaultSone(mocks.database, "1234567890123456789012345678901234567890123", false, null))) + )); + } + + @Test + public void linkToTooShortSoneIdIsParsedAsPlainText() throws IOException { + assertThat(parse("sone://Sone"), matches( + is(new PlainTextPart("sone://Sone")) + )); + } + + @Test + public void cutOffQueryFromTextOfFreenetLink() throws IOException { + assertThat(parse("KSK@gpl.txt?max-size=17"), matches( + is(new FreenetLinkPart("KSK@gpl.txt?max-size=17", "gpl.txt", false)) + )); + } + + @Test + public void linkWithoutMetaInformationShowsShortenedRoutingKey() throws IOException { + assertThat(parse("CHK@DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU"), matches( + is(new FreenetLinkPart("CHK@DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU", "CHK@DAxKQ", false)) + )); + } + + @Test + public void httpLinkGetsPartOfPathRemoved() throws IOException { + assertThat(parse("http://server.com/path/foo/test.html"), matches( + is(new LinkPart("http://server.com/path/foo/test.html", "server.com/…/test.html")) + )); + } + + @Test + public void httpLinkThatEndsInASlashGetsSlashRemoved() throws IOException { + assertThat(parse("http://server.com/path/foo/"), matches( + is(new LinkPart("http://server.com/path/foo/", "server.com/…")) + )); + } + + @Test + public void httpLinkGetsWwwRemoved() throws IOException { + assertThat(parse("http://www.server.com/foo.html"), matches( + is(new LinkPart("http://www.server.com/foo.html", "server.com/foo.html")) + )); + } + + @Test + public void httpLinkGetsQueryRemoved() throws IOException { + assertThat(parse("http://server.com/foo.html?id=4"), matches( + is(new LinkPart("http://server.com/foo.html?id=4", "server.com/foo.html")) + )); } } -- 2.7.4