From e121ca0a629580422fb4ca6af998f19e5b22f033 Mon Sep 17 00:00:00 2001 From: Tobias Thierer Date: Tue, 8 Sep 2020 17:32:13 +0100 Subject: [PATCH] HexDumpTest: provide test coverage While debugging bug 160801497, I discovered that HexDump would throw unusual exception types (RuntimeException, StringIndexOutOfBoundsException) when given a String with non-hex characters or odd length. Further investigation found that HexDumpTest provided minimal coverage, and one of its test methods no longer passes. This CL fixes the broken test, and adds more coverage. The unusual exception types thrown on invalid hex strings are now formalized in the test. Test: atest FrameworksCoreTests:com.android.internal.util.HexDumpTest Change-Id: I1dc24f803c303e3457b856d62088e09c046e31e3 --- .../android/internal/util/HexDumpTest.java | 147 +++++++++++++++++- 1 file changed, 145 insertions(+), 2 deletions(-) diff --git a/core/tests/coretests/src/com/android/internal/util/HexDumpTest.java b/core/tests/coretests/src/com/android/internal/util/HexDumpTest.java index 359bd5e93daeb..4f0becc8ad5d8 100644 --- a/core/tests/coretests/src/com/android/internal/util/HexDumpTest.java +++ b/core/tests/coretests/src/com/android/internal/util/HexDumpTest.java @@ -16,8 +16,16 @@ package com.android.internal.util; +import static com.android.internal.util.HexDump.hexStringToByteArray; +import static com.android.internal.util.HexDump.toHexString; + import junit.framework.TestCase; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.Random; + public final class HexDumpTest extends TestCase { public void testBytesToHexString() { assertEquals("abcdef", HexDump.toHexString( @@ -25,7 +33,142 @@ public final class HexDumpTest extends TestCase { assertEquals("ABCDEF", HexDump.toHexString( new byte[] { (byte) 0xab, (byte) 0xcd, (byte) 0xef }, true)); } - public void testNullArray() { - assertEquals("(null)", HexDump.toHexString(null)); + + public void testNullByteArray() { + assertThrows( + NullPointerException.class, + () -> HexDump.toHexString(null)); } + + public void testBytesToHexString_allByteValues() { + byte[] bytes = new byte[256]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = (byte) (i % 256); + } + + StringBuilder sb = new StringBuilder(); + for (char firstChar : "0123456789ABCDEF".toCharArray()) { + for (char secondChar : "0123456789ABCDEF".toCharArray()) { + sb.append(firstChar).append(secondChar); + } + } + String expected = sb.toString(); + + assertEquals(expected, HexDump.toHexString(bytes)); + } + + public void testRoundTrip_fromBytes() { + Random deterministicRandom = new Random(31337); // arbitrary but deterministic + for (int length = 0; length < 100; length++) { + byte[] bytes = new byte[length]; + deterministicRandom.nextBytes(bytes); + byte[] reconstruction = hexStringToByteArray(toHexString(bytes)); + + assertBytesEqual(bytes, reconstruction); + } + } + + public void testRoundTrip_fromString() { + String hexString = "0123456789ABCDEF72f9a3438934c378d34f32a8b932"; + for (int length = 0; length < hexString.length(); length += 2) { + String original = hexString.substring(0, length); + String reconstruction = toHexString(hexStringToByteArray(original)); + assertEquals(original.toUpperCase(), reconstruction); + } + } + + public void testToHexString_offsetLength() { + byte[] bytes = new byte[32]; + for (int i = 0; i < 16; i++) { + bytes[i] = (byte) i; + bytes[16 + i] = (byte) (16 * i); + } + String expected = "000102030405060708090A0B0C0D0E0F00102030405060708090A0B0C0D0E0F0"; + for (int offset = 0; offset < bytes.length; offset++) { + for (int len = 0; len < (bytes.length - offset); len++) { + + byte[] subBytes = new byte[len]; + System.arraycopy(bytes, offset, subBytes, 0, len); + + String actual = toHexString(bytes, offset, len); + assertEquals(expected.substring(2 * offset, 2 * offset + 2 * len), actual); + assertEquals(toHexString(subBytes), actual); + } + } + } + + public void testToHexString_case() { + byte[] bytes = new byte[32]; + for (int i = 0; i < 16; i++) { + bytes[i] = (byte) i; + bytes[16 + i] = (byte) (16 * i); + } + + String expected = "000102030405060708090A0B0C0D0E0F00102030405060708090A0B0C0D0E0F0"; + + assertEquals(expected.toUpperCase(), toHexString(bytes, true)); + assertEquals(expected.toLowerCase(), toHexString(bytes, false)); + + // default is uppercase + assertEquals(expected.toUpperCase(), toHexString(bytes)); + } + + public void testHexStringToByteArray_empty() { + assertBytesEqual(new byte[0], HexDump.hexStringToByteArray("")); + } + + public void testHexStringToByteArray_null() { + assertThrows( + NullPointerException.class, + () -> HexDump.hexStringToByteArray((String) null)); + } + + public void testHexStringToByteArray_invalidCharacters() { + // IllegalArgumentException would probably have been better than RuntimeException, but it + // might be too late to change now. + assertThrows( + RuntimeException.class, + () -> HexDump.hexStringToByteArray("GG")); + assertThrows( + RuntimeException.class, + () -> HexDump.hexStringToByteArray("\0\0")); + assertThrows( + RuntimeException.class, + () -> HexDump.hexStringToByteArray("abcdefgh")); + } + + public void testHexStringToByteArray_oddLength() { + // IllegalArgumentException would probably have been better than + // StringIndexOutOfBoundsException, but it might be too late to change now. + assertThrows( + StringIndexOutOfBoundsException.class, + () -> HexDump.hexStringToByteArray("A")); + assertThrows( + StringIndexOutOfBoundsException.class, + () -> HexDump.hexStringToByteArray("123")); + assertThrows( + StringIndexOutOfBoundsException.class, + () -> HexDump.hexStringToByteArray("ABCDE")); + } + + private static void assertBytesEqual(byte[] expected, byte[] actual) { + if (!Arrays.equals(expected, actual)) { + fail("Expected " + Arrays.toString(expected) + ", got " + Arrays.toString(actual)); + } + } + + private static void assertThrows(Class clazz, Runnable runnable) { + try { + runnable.run(); + } catch (RuntimeException exception) { + assertEquals(toStrackTrace(exception), clazz, exception.getClass()); + } + } + + private static String toStrackTrace(Throwable throwable) { + StringWriter stringWriter = new StringWriter(); + throwable.printStackTrace(new PrintWriter(stringWriter)); + return stringWriter.toString(); + } + }