/* * 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 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 = 1024 * 1024 * 5; testStringSerializedDeserializesToSameValue(stringGenerator.randomString(strlen)); } 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 randomNumberGenerator; 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(); randomNumberGenerator = 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 randomNumberGenerator.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(randomNumberGenerator.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(); } } }