[GEODE-2191] Test randomized string serialization/deserialization.
Project: http://git-wip-us.apache.org/repos/asf/geode/repo Commit: http://git-wip-us.apache.org/repos/asf/geode/commit/1d49bc18 Tree: http://git-wip-us.apache.org/repos/asf/geode/tree/1d49bc18 Diff: http://git-wip-us.apache.org/repos/asf/geode/diff/1d49bc18 Branch: refs/heads/master Commit: 1d49bc18cc0c6a77fd739f66545bf2f3a21cc9bf Parents: 610ae1d Author: Galen O'Sullivan <[email protected]> Authored: Thu Dec 8 10:43:55 2016 -0800 Committer: Udo Kohlmeyer <[email protected]> Committed: Tue Jan 24 10:17:10 2017 -0800 ---------------------------------------------------------------------- ...ternalDataSerializerRandomizedJUnitTest.java | 184 +++++++++++++++++++ 1 file changed, 184 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/geode/blob/1d49bc18/geode-core/src/test/java/org/apache/geode/internal/InternalDataSerializerRandomizedJUnitTest.java ---------------------------------------------------------------------- diff --git a/geode-core/src/test/java/org/apache/geode/internal/InternalDataSerializerRandomizedJUnitTest.java b/geode-core/src/test/java/org/apache/geode/internal/InternalDataSerializerRandomizedJUnitTest.java new file mode 100644 index 0000000..0fe2817 --- /dev/null +++ b/geode-core/src/test/java/org/apache/geode/internal/InternalDataSerializerRandomizedJUnitTest.java @@ -0,0 +1,184 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.apache.geode.internal; + +import static org.junit.Assert.*; + +import org.apache.geode.DataSerializer; +import org.apache.geode.cache.query.functional.IndexUsageInNestedQueryJUnitTest; +import org.apache.geode.test.junit.categories.UnitTest; +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import javax.xml.bind.DatatypeConverter; +import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.Inet4Address; +import java.security.SecureRandom; + +/** + * Tests the serialization and deserialization of strings made up of random Unicode code points. + */ +@Category(UnitTest.class) +public class InternalDataSerializerRandomizedJUnitTest { + private static final int ITERATIONS = 1000; + + private static class RandomStringGenerator { + public static final int SEED_BYTES = 8; + public static final int MAX_STRING_LENGTH = 65538; + // UTF-16 can represent any codepoint with 2 chars. + public static final int MAX_UTF16_CHARS = MAX_STRING_LENGTH * 2; + public static final int UNICODE_MAX = Character.MAX_CODE_POINT; + private final byte[] seed; + private final SecureRandom rng; + + public byte[] getSeed() { + return seed.clone(); + } + + /** + * Returns a string representation of the seed, in xsd:hexBinary. This can be passed directly to + * the String constructor of this class to recreate with the same seed. + */ + public String getSeedString() { + return DatatypeConverter.printHexBinary(seed); + } + + /** + * Generate and return a random string made up of a series of Unicode codepoints (integers). + * This can be any series of codepoints, even unreserved ones. + */ + public RandomStringGenerator() { + this(SecureRandom.getSeed(SEED_BYTES)); + } + + /** + * Construct based on a provided seed. Mostly this should be useful for reproducing tests. + * <p> + * Technically we can take any size of seed, but we use one of size SEED_BYTES for tests. + */ + public RandomStringGenerator(byte[] seed) { + this.seed = seed.clone(); + rng = new SecureRandom(this.seed); + } + + /** + * @param seedString an xsd:hexBinary string representing a seed. Normally acquired from + * `getSeedString();` + */ + public RandomStringGenerator(String seedString) { + this(DatatypeConverter.parseHexBinary(seedString)); + } + + public int randomCodepoint() { + return rng.nextInt(UNICODE_MAX); + } + + /** + * @return A random string made of Unicode codepoints in the range from 0 to UNICODE_MAX. These + * strings will not necessarily be valid, as some codepoints in that range may be + * unallocated in the Unicode spec. + */ + public String randomString() { + return randomString(rng.nextInt(MAX_UTF16_CHARS)); + } + + public String randomString(int length) { + StringBuilder stringBuilder = new StringBuilder(length * 2); + + for (int i = 0; i < length; i++) { + int codepoint = randomCodepoint(); + try { + stringBuilder.appendCodePoint(codepoint); + } catch (IllegalArgumentException ex) { + System.out.println("Generated illegal codepoint " + codepoint); + } + } + return stringBuilder.toString(); + } + } + + + private static void testStringSerializedDeserializesToSameValue(String originalString) + throws IOException { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream); + + DataSerializer.writeString(originalString, dataOutputStream); + dataOutputStream.flush(); + + byte[] stringBytes = byteArrayOutputStream.toByteArray(); + DataInputStream dataInputStream = new DataInputStream(new ByteArrayInputStream(stringBytes)); + String returnedString = DataSerializer.readString(dataInputStream); + + assertEquals("Deserialized string matches original", originalString, returnedString); + } + + @Before + public void setUp() { + // this may be unnecessary, but who knows what tests run before us. + InternalDataSerializer.reinitialize(); + } + + @Test + public void testRandomStringsSerializeThenDeserializeToSameValues() throws Exception { + RandomStringGenerator stringGenerator = new RandomStringGenerator(); + try { + for (int i = 0; i < ITERATIONS; i++) { + String str = stringGenerator.randomString(); + testStringSerializedDeserializesToSameValue(str); + } + } catch (Throwable throwable) { + throw new Exception("Failed with seed " + stringGenerator.getSeedString(), throwable); + } + } + + @Test + public void testEdgeCaseSerializationDeserialization() throws IOException { + testStringSerializedDeserializesToSameValue("\0"); + testStringSerializedDeserializesToSameValue(""); + + RandomStringGenerator stringGenerator = new RandomStringGenerator(); + testStringSerializedDeserializesToSameValue(stringGenerator.randomString(65534)); + testStringSerializedDeserializesToSameValue(stringGenerator.randomString(65535)); + testStringSerializedDeserializesToSameValue(stringGenerator.randomString(65536)); + testStringSerializedDeserializesToSameValue(stringGenerator.randomString(65537)); + + testStringSerializedDeserializesToSameValue(stringGenerator.randomString()); + } + + @Test + public void testABigString() throws IOException { + RandomStringGenerator stringGenerator = new RandomStringGenerator(); + final int strlen = 128001; + + StringBuilder stringBuilder = new StringBuilder(strlen); + while (stringBuilder.length() < strlen) { + stringBuilder.append(stringGenerator.randomCodepoint()); + } + // the last few we have to worry about codepoinuts being too big. + while (stringBuilder.length() < strlen) { + int codepoint = stringGenerator.randomCodepoint(); + if (codepoint <= strlen) { + stringBuilder.append(codepoint); + } + } + testStringSerializedDeserializesToSameValue(stringBuilder.toString()); + } +}
