From: David ‘Bombe’ Roden Date: Sun, 12 Feb 2023 12:48:29 +0000 (+0100) Subject: ✅ Add test for FcpUtils X-Git-Url: https://git.pterodactylus.net/?a=commitdiff_plain;h=35274748b09a236c22ed907cc8042df7ccf70e31;p=jFCPlib.git ✅ Add test for FcpUtils Also, fix a bug with the long-parsing. --- diff --git a/src/main/java/net/pterodactylus/fcp/FcpUtils.java b/src/main/java/net/pterodactylus/fcp/FcpUtils.java index 0005f8f..50b9d7b 100644 --- a/src/main/java/net/pterodactylus/fcp/FcpUtils.java +++ b/src/main/java/net/pterodactylus/fcp/FcpUtils.java @@ -17,20 +17,20 @@ package net.pterodactylus.fcp; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.EOFException; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; -import java.util.StringTokenizer; import java.util.concurrent.atomic.AtomicLong; +import static java.lang.String.format; +import static java.lang.String.join; +import static java.lang.System.currentTimeMillis; +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.joining; + /** * Helper class with utility methods for the FCP protocol. * @@ -47,7 +47,7 @@ public class FcpUtils { * @return A unique identifier */ public static String getUniqueIdentifier() { - return new StringBuilder().append(System.currentTimeMillis()).append('-').append(counter.getAndIncrement()).toString(); + return format("%d-%d", currentTimeMillis(), counter.getAndIncrement()); } /** @@ -60,14 +60,7 @@ public class FcpUtils { * if a value can not be converted to a number */ public static int[] decodeMultiIntegerField(String field) throws NumberFormatException { - StringTokenizer fieldTokens = new StringTokenizer(field, ";"); - int[] result = new int[fieldTokens.countTokens()]; - int counter = 0; - while (fieldTokens.hasMoreTokens()) { - String fieldToken = fieldTokens.nextToken(); - result[counter++] = Integer.valueOf(fieldToken); - } - return result; + return stream(field.split(";")).mapToInt(Integer::parseInt).toArray(); } /** @@ -79,14 +72,7 @@ public class FcpUtils { * @return The encoded values */ public static String encodeMultiIntegerField(int[] values) { - StringBuilder encodedField = new StringBuilder(); - for (int value : values) { - if (encodedField.length() > 0) { - encodedField.append(';'); - } - encodedField.append(value); - } - return encodedField.toString(); + return stream(values).mapToObj(String::valueOf).collect(joining(";")); } /** @@ -98,14 +84,7 @@ public class FcpUtils { * @return The encoded values */ public static String encodeMultiStringField(String[] values) { - StringBuilder encodedField = new StringBuilder(); - for (String value : values) { - if (encodedField.length() > 0) { - encodedField.append(';'); - } - encodedField.append(value); - } - return encodedField.toString(); + return join(";", values); } /** @@ -132,7 +111,7 @@ public class FcpUtils { */ public static int safeParseInt(String value, int defaultValue) { try { - return Integer.valueOf(value); + return Integer.parseInt(value); } catch (NumberFormatException nfe1) { return defaultValue; } @@ -162,7 +141,7 @@ public class FcpUtils { */ public static long safeParseLong(String value, long defaultValue) { try { - return Integer.valueOf(value); + return Long.parseLong(value); } catch (NumberFormatException nfe1) { return defaultValue; } diff --git a/src/test/java/net/pterodactylus/fcp/FcpUtilsTest.java b/src/test/java/net/pterodactylus/fcp/FcpUtilsTest.java new file mode 100644 index 0000000..904e14d --- /dev/null +++ b/src/test/java/net/pterodactylus/fcp/FcpUtilsTest.java @@ -0,0 +1,258 @@ +package net.pterodactylus.fcp; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +import org.junit.Test; + +import static java.util.Collections.synchronizedSet; +import static net.pterodactylus.fcp.FcpUtils.close; +import static net.pterodactylus.fcp.FcpUtils.copy; +import static net.pterodactylus.fcp.FcpUtils.decodeMultiIntegerField; +import static net.pterodactylus.fcp.FcpUtils.encodeMultiIntegerField; +import static net.pterodactylus.fcp.FcpUtils.encodeMultiStringField; +import static net.pterodactylus.fcp.FcpUtils.getUniqueIdentifier; +import static net.pterodactylus.fcp.FcpUtils.safeParseInt; +import static net.pterodactylus.fcp.FcpUtils.safeParseLong; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.assertThrows; + +/** + * Unit test for {@link FcpUtils}. + */ +public class FcpUtilsTest { + + @Test + public void uniqueIdentifiersAreIndeedAtLeastSomewhatUnique() { + Set identifiers = synchronizedSet(new HashSet<>(100 * 10000)); + List threads = new ArrayList<>(); + for (int threadIndex = 0; threadIndex < 100; threadIndex++) { + Thread thread = new Thread(() -> { + for (int index = 0; index < 10000; index++) { + identifiers.add(getUniqueIdentifier()); + } + }); + threads.add(thread); + thread.start(); + } + threads.forEach(thread -> { + try { + thread.join(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + assertThat(identifiers, hasSize(100 * 10000)); + } + + @Test + public void decodeMultiIntegerFieldCanDecodeASingleInteger() { + assertThat(decodeMultiIntegerField("123"), equalTo(new int[] { 123 })); + } + + @Test + public void decodeMultiIntegerFieldCanDecodeMultipleIntegers() { + assertThat(decodeMultiIntegerField("123;234;345"), equalTo(new int[] { 123, 234, 345 })); + } + + @Test + public void decodeMultiIntegerFieldThrowsExceptionIfSingleFieldIsNotANumber() { + assertThrows(NumberFormatException.class, () -> decodeMultiIntegerField("not-a-number")); + } + + @Test + public void decodeMultiIntegerFieldThrowsExceptionIfMultipleFieldsContainNotANumber() { + assertThrows(NumberFormatException.class, () -> decodeMultiIntegerField("1;not-a-number;3")); + } + + @Test + public void encodeMultiIntegerFieldEncodesASingleIntegerCorrectly() { + assertThat(encodeMultiIntegerField(new int[] { 123 }), equalTo("123")); + } + + @Test + public void encodeMultiIntegerFieldEncodesMultipleIntegersCorrectly() { + assertThat(encodeMultiIntegerField(new int[] { 123, 234, 345 }), equalTo("123;234;345")); + } + + @Test + public void encodeMultiStringFieldEncodesASingleStringCorrectly() { + assertThat(encodeMultiStringField(new String[] { "abc" }), equalTo("abc")); + } + + @Test + public void encodeMultiStringFieldEncodesMultipleStringsCorrectly() { + assertThat(encodeMultiStringField(new String[] { "abc", "def", "ghi" }), equalTo("abc;def;ghi")); + } + + @Test + public void safeParseIntCanParseANumericStringCorrectly() { + assertThat(safeParseInt("123"), equalTo(123)); + } + + @Test + public void safeParseIntReturnsMinus1OnInvalidNumber() { + assertThat(safeParseInt("not-a-number"), equalTo(-1)); + } + + @Test + public void safeParseIntWithDefaultValueCanParseANumericStringCorrectly() { + assertThat(safeParseInt("123", 234), equalTo(123)); + } + + @Test + public void safeParseIntWithDefaultValueReturnsDefaultValueOnInvalidNumericString() { + assertThat(safeParseInt("not-a-number", 234), equalTo(234)); + } + + @Test + public void safeParseLongCanParseANumericStringCorrectly() { + assertThat(safeParseLong("12345678901"), equalTo(12345678901L)); + } + + @Test + public void safeParseLongReturnsMinus1OnInvalidNumber() { + assertThat(safeParseLong("not-a-number"), equalTo(-1L)); + } + + @Test + public void safeParseLongWithDefaultValueCanParseANumericStringCorrectly() { + assertThat(safeParseLong("12345678901", 234), equalTo(12345678901L)); + } + + @Test + public void safeParseLongWithDefaultValueReturnsDefaultValueOnInvalidNumericString() { + assertThat(safeParseLong("not-a-number", 234), equalTo(234L)); + } + + @Test + public void socketIsClosed() { + AtomicBoolean closed = new AtomicBoolean(false); + Socket socket = new Socket() { + @Override + public void close() { + closed.set(true); + } + }; + close(socket); + assertThat(closed.get(), equalTo(true)); + } + + @Test + public void exceptionWhileClosingASocketIsIgnored() { + Socket socket = new Socket() { + @Override + public void close() throws IOException { + throw new IOException(); + } + }; + close(socket); + } + + @Test + public void closingANullSocketDoesNotDoAnything() { + close(null); + } + + @Test + public void closingACloseableClosesTheCloseable() { + AtomicBoolean closed = new AtomicBoolean(false); + Closeable closeable = () -> closed.set(true); + close(closeable); + assertThat(closed.get(), equalTo(true)); + } + + @Test + public void exceptionWhileClosingACloseableIsIgnored() { + Closeable closeable = () -> { + throw new IOException(); + }; + close(closeable); + } + + @Test + public void closingANullCloseableDoesNothing() { + close((Closeable) null); + } + + @Test + public void copyingAStreamCopiesAllOfItsContents() throws IOException { + AtomicLong writtenBytes = new AtomicLong(0); + try (InputStream largeInputStream = getLimitedInputStream(); + OutputStream outputStream = getCountingOutputStream(writtenBytes)) { + copy(largeInputStream, outputStream); + } + assertThat(writtenBytes.get(), equalTo(1024 * 1024 * 1024L)); + } + + @Test + public void copyingAStreamWithALimitCopiesOnlyTheRequestedAmountOfBytes() throws IOException { + AtomicLong writtenBytes = new AtomicLong(0); + try (InputStream largeInputStream = getLimitedInputStream(); + OutputStream outputStream = getCountingOutputStream(writtenBytes)) { + copy(largeInputStream, outputStream, 1024 * 1024); + } + assertThat(writtenBytes.get(), equalTo(1024 * 1024L)); + } + + @Test + public void tryingToCopyMoreBytesThanThereAreInTheInputStreamThrowsEofException() { + assertThrows(EOFException.class, () -> copy(new ByteArrayInputStream(new byte[] { 1, 2, 3 }), new ByteArrayOutputStream(), 4)); + } + + private static InputStream getLimitedInputStream() { + return new InputStream() { + private long remaining = 1024 * 1024 * 1024; + + @Override + public int read(byte[] b, int off, int len) { + if (remaining == 0) { + return -1; + } + long maxToRead = Math.min(remaining, len); + for (int index = 0; index < maxToRead; index++) { + b[off + index] = (byte) --remaining; + } + return (int) maxToRead; + } + + @Override + public int read() { + remaining--; + if (remaining == -1) { + return -1; + } + return (int) (remaining & 0xff); + } + }; + } + + private static OutputStream getCountingOutputStream(AtomicLong writtenBytes) { + return new OutputStream() { + @Override + public void write(int b) { + writtenBytes.incrementAndGet(); + } + + @Override + public void write(byte[] b, int off, int len) { + writtenBytes.addAndGet(len); + } + }; + } + +}