diff options
Diffstat (limited to 'lib/java/test/org')
12 files changed, 2434 insertions, 0 deletions
diff --git a/lib/java/test/org/apache/thrift/TestPartialThriftDeserializer.java b/lib/java/test/org/apache/thrift/TestPartialThriftDeserializer.java new file mode 100644 index 000000000..c0c7b892d --- /dev/null +++ b/lib/java/test/org/apache/thrift/TestPartialThriftDeserializer.java @@ -0,0 +1,580 @@ +/* + * 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.thrift.partial; + +import static org.junit.Assert.*; + +import org.apache.thrift.partial.TestStruct; +import org.apache.thrift.partial.ThriftField; +import org.apache.thrift.partial.TstEnum; +import org.apache.thrift.partial.ExceptionAsserts; + +import org.apache.thrift.TBase; +import org.apache.thrift.TDeserializer; +import org.apache.thrift.TException; +import org.apache.thrift.TSerializer; +import org.apache.thrift.protocol.TBinaryProtocol; +import org.apache.thrift.protocol.TCompactProtocol; +import org.junit.Test; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class TestPartialThriftDeserializer { + + private ThriftSerDe serde = new ThriftSerDe(); + private TBinaryProtocol.Factory binaryProtocolFactory = new TBinaryProtocol.Factory(); + private TCompactProtocol.Factory compactProtocolFactory = new TCompactProtocol.Factory(); + + private PartialThriftTestData testData = new PartialThriftTestData(); + + public TestPartialThriftDeserializer() throws TException { + } + + @Test + public void testArgChecks() throws TException { + // Should not throw. + List<String> fieldNames = Arrays.asList("i32Field"); + new TDeserializer(TestStruct.class, fieldNames, binaryProtocolFactory); + + // Verify it throws correctly. + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'thriftClass' must not be null", + () -> new TDeserializer(null, fieldNames, binaryProtocolFactory)); + + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'fieldNames' must not be null", + () -> new TDeserializer(TestStruct.class, null, binaryProtocolFactory)); + + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'processor' must not be null", + () -> new TDeserializer(TestStruct.class, fieldNames, null, binaryProtocolFactory)); + } + + /** + * This test does not use partial deserialization. It is used to establish correctness + * of full serialization used in the other tests. + */ + @Test + public void testRoundTripFull() throws TException { + TestStruct ts1 = testData.createTestStruct(1, 2); + + byte[] bytesBinary = serde.serializeBinary(ts1); + byte[] bytesCompact = serde.serializeCompact(ts1); + + TestStruct ts2 = serde.deserializeBinary(bytesBinary, TestStruct.class); + assertEquals(ts1, ts2); + + ts2 = serde.deserializeCompact(bytesCompact, TestStruct.class); + assertEquals(ts1, ts2); + } + + @Test + public void testPartialSimpleField() throws TException, IOException { + TestStruct ts1 = testData.createTestStruct(1, 1); + assertTrue(ts1.isSetI16Field()); + assertTrue(ts1.isSetI32Field()); + + byte[] bytesBinary = serde.serializeBinary(ts1); + byte[] bytesCompact = serde.serializeCompact(ts1); + + List<String> fieldNames = Arrays.asList("i32Field"); + + TDeserializer partialBinaryDeserializer = + new TDeserializer(TestStruct.class, fieldNames, binaryProtocolFactory); + TDeserializer partialCompactDeserializer = + new TDeserializer(TestStruct.class, fieldNames, compactProtocolFactory); + + PartialThriftComparer comparer = + new PartialThriftComparer(partialBinaryDeserializer.getMetadata()); + + StringBuilder sb = new StringBuilder(); + TestStruct ts2 = (TestStruct) partialBinaryDeserializer.partialDeserializeObject(bytesBinary); + validatePartialSimpleField(ts1, ts2); + if (!comparer.areEqual(ts1, ts2, sb)) { + fail(sb.toString()); + } + + ts2 = (TestStruct) partialCompactDeserializer.partialDeserializeObject(bytesCompact); + validatePartialSimpleField(ts1, ts2); + if (!comparer.areEqual(ts1, ts2, sb)) { + fail(sb.toString()); + } + } + + private void validatePartialSimpleField(TestStruct ts1, TestStruct ts2) { + assertTrue(ts2.toString(), ts2.isSetI32Field()); + assertEquals(ts1.getI32Field(), ts2.getI32Field()); + assertFalse(ts2.isSetI16Field()); + } + + @Test + public void testPartialComplex() throws TException { + int id = 1; + int numItems = 10; + TestStruct ts1 = testData.createTestStruct(id, numItems); + + byte[] bytesBinary = serde.serializeBinary(ts1); + byte[] bytesCompact = serde.serializeCompact(ts1); + + List<String> fieldNames = Arrays.asList( + "byteField", + "i16Field", + "i32Field", + "i64Field", + "doubleField", + "stringField", + + "enumField", + "binaryField", + + // List fields + "byteList", + "i16List", + "i32List", + "i64List", + "doubleList", + "stringList", + "enumList", + "listList", + "setList", + "mapList", + "structList", + "binaryList", + + // Set fields + "byteSet", + "i16Set", + "i32Set", + "i64Set", + "doubleSet", + "stringSet", + "enumSet", + "listSet", + "setSet", + "mapSet", + "structSet", + "binarySet", + + // Map fields + "byteMap", + "i16Map", + "i32Map", + "i64Map", + "doubleMap", + "stringMap", + "enumMap", + "listMap", + "setMap", + "mapMap", + "structMap", + "binaryMap", + + // Struct field + "structField" + ); + StringBuilder sb = new StringBuilder(); + TDeserializer partialBinaryDeserializer = + new TDeserializer(TestStruct.class, fieldNames, binaryProtocolFactory); + TDeserializer partialCompactDeserializer = + new TDeserializer(TestStruct.class, fieldNames, compactProtocolFactory); + PartialThriftComparer comparer = + new PartialThriftComparer(partialBinaryDeserializer.getMetadata()); + + TestStruct ts2 = (TestStruct) partialBinaryDeserializer.partialDeserializeObject(bytesBinary); + validatePartialComplex(ts1, ts2, id, numItems); + if (!comparer.areEqual(ts1, ts2, sb)) { + fail(sb.toString()); + } + + ts2 = (TestStruct) partialCompactDeserializer.partialDeserializeObject(bytesCompact); + validatePartialComplex(ts1, ts2, id, numItems); + if (!comparer.areEqual(ts1, ts2, sb)) { + fail(sb.toString()); + } + } + + private void validatePartialComplex(TestStruct ts1, TestStruct ts2, int id, int numItems) { + + // Validate primitive fields. + assertTrue(ts2.toString(), ts2.isSetByteField()); + assertEquals(ts1.getByteField(), ts2.getByteField()); + + assertTrue(ts2.isSetI16Field()); + assertEquals(ts1.getI16Field(), ts2.getI16Field()); + + assertTrue(ts2.isSetI32Field()); + assertEquals(ts1.getI32Field(), ts2.getI32Field()); + + assertTrue(ts2.isSetI64Field()); + assertEquals(ts1.getI64Field(), ts2.getI64Field()); + + assertTrue(ts2.isSetDoubleField()); + assertEquals(ts1.getDoubleField(), ts2.getDoubleField(), 0.0001); + + assertTrue(ts2.isSetStringField()); + assertEquals(ts1.getStringField(), ts2.getStringField()); + + assertTrue(ts2.isSetEnumField()); + assertEquals(ts1.getEnumField(), ts2.getEnumField()); + + assertTrue(ts2.isSetBinaryField()); + assertArrayEquals(ts1.getBinaryField(), ts2.getBinaryField()); + + // Validate list fields. + validateList(ts2.getByteList(), id, numItems); + validateList(ts2.getI16List(), id, numItems); + validateList(ts2.getI32List(), id, numItems); + validateList(ts2.getI64List(), id, numItems); + validateList(ts2.getDoubleList(), id, numItems); + validateStringList(ts2.getStringList(), id, numItems); + validateEnumList(ts2.getEnumList(), id, numItems); + + validateListOfList(ts2.getListList(), id, numItems); + validateListOfSet(ts2.getSetList(), id, numItems); + validateListOfMap(ts2.getMapList(), id, numItems); + validateListOfStruct(ts2.getStructList(), id, numItems); + validateListOfBinary(ts2.getBinaryList(), id, numItems); + + // Validate set fields. + validateSet(ts2.getByteSet(), Byte.class, numItems); + validateSet(ts2.getI16Set(), Short.class, numItems); + validateSet(ts2.getI32Set(), Integer.class, numItems); + validateSet(ts2.getI64Set(), Long.class, numItems); + validateSet(ts2.getDoubleSet(), Double.class, numItems); + validateStringSet(ts2.getStringSet(), id, numItems); + validateEnumSet(ts2.getEnumSet(), id, numItems); + + validateSetOfList(ts2.getListSet(), id, numItems); + validateSetOfSet(ts2.getSetSet(), id, numItems); + validateSetOfMap(ts2.getMapSet(), id, numItems); + validateSetOfStruct(ts2.getStructSet(), id, numItems); + validateSetOfBinary(ts2.getBinarySet(), id, numItems); + + // Validate map fields. + validateMap(ts2.getByteMap(), Byte.class, numItems); + validateMap(ts2.getI16Map(), Short.class, numItems); + validateMap(ts2.getI32Map(), Integer.class, numItems); + validateMap(ts2.getI64Map(), Long.class, numItems); + validateMap(ts2.getDoubleMap(), Double.class, numItems); + validateStringMap(ts2.getStringMap(), id, numItems); + validateEnumMap(ts2.getEnumMap(), id, numItems); + + validateMapOfList(ts2.getListMap(), id, numItems); + validateMapOfSet(ts2.getSetMap(), id, numItems); + validateMapOfMap(ts2.getMapMap(), id, numItems); + validateMapOfStruct(ts2.getStructMap(), id, numItems); + validateMapOfBinary(ts2.getBinaryMap(), id, numItems); + + // Validate struct field. + assertEquals(testData.createSmallStruct(id), ts2.getStructField()); + } + + private void validateNotNullAndNotEmpty(Collection<?> collection, int numItems) { + assertNotNull(collection); + assertEquals(numItems, collection.size()); + } + + // ---------------------------------------------------------------------- + // List validation helpers. + + private <V extends Number> void validateList(List<V> list, int id, int numItems) { + validateNotNullAndNotEmpty(list, numItems); + + for (int i = 0; i < numItems; i++) { + assertEquals(i, list.get(i).longValue()); + } + } + + private void validateStringList(List<String> list, int id, int numItems) { + validateNotNullAndNotEmpty(list, numItems); + for (int i = 0; i < numItems; i++) { + assertEquals(Integer.valueOf(i), Integer.valueOf(list.get(i))); + } + } + + private void validateEnumList(List<TstEnum> list, int id, int numItems) { + validateNotNullAndNotEmpty(list, numItems); + for (int i = 0; i < numItems; i++) { + assertEquals(TstEnum.E_ONE, list.get(i)); + } + } + + private <V extends Number> void validateListOfList(List<List<V>> list, int id, int numItems) { + validateNotNullAndNotEmpty(list, numItems); + + for (int i = 0; i < numItems; i++) { + validateList(list.get(i), id, numItems); + } + } + + private <V extends Number> void validateListOfSet(List<Set<V>> list, int id, int numItems) { + validateNotNullAndNotEmpty(list, numItems); + + for (int i = 0; i < numItems; i++) { + Set<V> set = list.get(i); + for (int j = 0; j < numItems; j++) { + assertTrue(set.contains(j)); + } + } + } + + private <V extends Number> void validateListOfMap( + List<Map<String, V>> list, int id, int numItems) { + + validateNotNullAndNotEmpty(list, numItems); + + for (int i = 0; i < numItems; i++) { + Map<String, V> map = list.get(i); + for (int j = 0; j < numItems; j++) { + String key = Integer.toString(j); + assertTrue(map.containsKey(key)); + assertEquals(j, map.get(key)); + } + } + } + + private void validateListOfStruct(List<SmallStruct> list, int id, int numItems) { + validateNotNullAndNotEmpty(list, numItems); + + for (int i = 0; i < numItems; i++) { + SmallStruct ss = testData.createSmallStruct(i); + for (int j = 0; j < numItems; j++) { + assertEquals(ss, list.get(i)); + } + } + } + + private void validateListOfBinary(List<ByteBuffer> list, int id, int numItems) { + validateNotNullAndNotEmpty(list, numItems); + + for (int i = 0; i < numItems; i++) { + ByteBuffer bb = ByteBuffer.wrap(testData.BYTES); + assertTrue(bb.compareTo(list.get(i)) == 0); + } + } + + // ---------------------------------------------------------------------- + // Set validation helpers. + + private <V extends Number> void validateSet(Set<V> set, Class<V> clasz, int numItems) { + validateNotNullAndNotEmpty(set, numItems); + + for (int i = 0; i < numItems; i++) { + if (clasz == Byte.class) { + assertTrue(set.contains((byte)i)); + } else if (clasz == Short.class) { + assertTrue(set.contains((short)i)); + } else if (clasz == Integer.class) { + assertTrue(set.contains(i)); + } else if (clasz == Long.class) { + assertTrue(set.contains((long)i)); + } else if (clasz == Double.class) { + assertTrue(set.contains((double)i)); + } + } + } + + private void validateStringSet(Set<String> set, int id, int numItems) { + validateNotNullAndNotEmpty(set, numItems); + + for (int i = 0; i < numItems; i++) { + assertTrue(set.contains(Integer.toString(i))); + } + } + + private void validateEnumSet(Set<TstEnum> set, int id, int numItems) { + validateNotNullAndNotEmpty(set, 1); + + assertTrue(set.contains(TstEnum.E_ONE)); + } + + private void validateSetOfList(Set<List<Integer>> set, int id, int numItems) { + validateNotNullAndNotEmpty(set, 1); + + List<Integer> list = new ArrayList<>(numItems); + for (int i = 0; i < numItems; i++) { + list.add(i); + } + + assertTrue(set.contains(list)); + } + + private void validateSetOfSet(Set<Set<Integer>> set, int id, int numItems) { + validateNotNullAndNotEmpty(set, 1); + + Set<Integer> setElt = new HashSet<>(); + for (int i = 0; i < numItems; i++) { + setElt.add(i); + } + + assertTrue(set.contains(setElt)); + } + + private void validateSetOfMap(Set<Map<String, Integer>> set, int id, int numItems) { + validateNotNullAndNotEmpty(set, 1); + + Map<String, Integer> map = new HashMap<>(); + for (int i = 0; i < numItems; i++) { + map.put(Integer.toString(i), i); + } + + assertTrue(set.contains(map)); + } + + private void validateSetOfStruct(Set<SmallStruct> set, int id, int numItems) { + validateNotNullAndNotEmpty(set, numItems); + + for (int i = 0; i < numItems; i++) { + SmallStruct ss = testData.createSmallStruct(i); + assertTrue(set.contains(ss)); + } + } + + private void validateSetOfBinary(Set<ByteBuffer> set, int id, int numItems) { + validateNotNullAndNotEmpty(set, 1); + + for (ByteBuffer b : set) { + ByteBuffer bb = ByteBuffer.wrap(testData.BYTES); + assertEquals(0, bb.compareTo(b)); + } + } + + // ---------------------------------------------------------------------- + // Map validation helpers. + + void validateNotNullAndNotEmpty(Map<?, ?> map, int numItems) { + assertNotNull(map); + assertEquals(numItems, map.size()); + } + + private <V extends Number> void validateMap(Map<V, V> map, Class<V> clasz, int numItems) { + validateNotNullAndNotEmpty(map, numItems); + + for (int i = 0; i < numItems; i++) { + if (clasz == Byte.class) { + assertTrue(map.containsKey((byte)i)); + assertEquals((byte) i, map.get((byte) i)); + } else if (clasz == Short.class) { + assertTrue(map.containsKey((short)i)); + assertEquals((short) i, map.get((short) i)); + } else if (clasz == Integer.class) { + assertTrue(map.containsKey(i)); + assertEquals(i, map.get(i)); + } else if (clasz == Long.class) { + assertTrue(map.containsKey((long)i)); + assertEquals((long) i, map.get((long) i)); + } else if (clasz == Double.class) { + assertTrue(map.containsKey((double)i)); + assertEquals((double) i, map.get((double) i)); + } + } + } + + private void validateStringMap(Map<String, String> map, int id, int numItems) { + validateNotNullAndNotEmpty(map, numItems); + + for (int i = 0; i < numItems; i++) { + String key = Integer.toString(i); + assertTrue(map.containsKey(key)); + assertEquals(key, map.get(key)); + } + } + + private void validateEnumMap(Map<TstEnum, TstEnum> map, int id, int numItems) { + validateNotNullAndNotEmpty(map, 1); + + assertTrue(map.containsKey(TstEnum.E_ONE)); + assertEquals(TstEnum.E_ONE, map.get(TstEnum.E_ONE)); + } + + private void validateMapOfList(Map<Integer, List<Integer>> map, int id, int numItems) { + validateNotNullAndNotEmpty(map, numItems); + + List<Integer> list = new ArrayList<>(numItems); + for (int i = 0; i < numItems; i++) { + list.add(i); + } + + for (int i = 0; i < numItems; i++) { + assertTrue(map.containsKey(i)); + assertEquals(list, map.get(i)); + } + } + + private void validateMapOfSet(Map<Integer, Set<Integer>> map, int id, int numItems) { + validateNotNullAndNotEmpty(map, numItems); + + Set<Integer> setElt = new HashSet<>(); + for (int i = 0; i < numItems; i++) { + setElt.add(i); + } + + for (int i = 0; i < numItems; i++) { + assertTrue(map.containsKey(i)); + assertEquals(setElt, map.get(i)); + } + } + + private void validateMapOfMap(Map<Integer, Map<Integer, Integer>> map, int id, int numItems) { + validateNotNullAndNotEmpty(map, numItems); + + Map<Integer, Integer> mapElt = new HashMap<>(); + for (int i = 0; i < numItems; i++) { + mapElt.put(i, i); + } + + for (int i = 0; i < numItems; i++) { + assertTrue(map.containsKey(i)); + assertEquals(mapElt, map.get(i)); + } + } + + private void validateMapOfStruct(Map<SmallStruct, SmallStruct> map, int id, int numItems) { + validateNotNullAndNotEmpty(map, numItems); + + for (int i = 0; i < numItems; i++) { + SmallStruct ss = testData.createSmallStruct(i); + assertTrue(map.containsKey(ss)); + assertEquals(ss, map.get(ss)); + } + } + + private void validateMapOfBinary(Map<Integer, ByteBuffer> map, int id, int numItems) { + validateNotNullAndNotEmpty(map, numItems); + + for (int i = 0; i < numItems; i++) { + ByteBuffer bb = ByteBuffer.wrap(testData.BYTES); + assertTrue(map.containsKey(i)); + assertEquals(0, bb.compareTo(map.get(i))); + } + } +} diff --git a/lib/java/test/org/apache/thrift/partial/EnumCacheTest.java b/lib/java/test/org/apache/thrift/partial/EnumCacheTest.java new file mode 100644 index 000000000..394dcc2e9 --- /dev/null +++ b/lib/java/test/org/apache/thrift/partial/EnumCacheTest.java @@ -0,0 +1,114 @@ +/* + * 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.thrift.partial; + +import org.apache.thrift.partial.ExceptionAsserts; + +import org.apache.thrift.TEnum; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Test ThriftCodec serializes and deserializes thrift objects correctly. + */ +public class EnumCacheTest { + + enum TestEnum implements TEnum { + Alice(-1), + Bob(0), + Charlie(1); + + private int value; + + TestEnum(int value) { + this.value = value; + } + + @Override + public int getValue() { + return this.value; + } + } + + static class NotEnum implements TEnum { + + public static final NotEnum Alice = new NotEnum(-11); + public static final NotEnum Bob = new NotEnum(10); + public static final NotEnum Charlie = new NotEnum(11); + + private static final NotEnum[] allValues = { Alice, Bob, Charlie }; + + private int value; + + private NotEnum(int value) { + this.value = value; + } + + public static TEnum[] values() { + return NotEnum.allValues; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String toString() { + return String.format("NotEnum : %d", this.value); + } + } + + @Test + public void testArgChecks() { + EnumCache cache = new EnumCache(); + + // Should not throw. + cache.get(TestEnum.class, 0); + + // Verify it throws. + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'enumClass' must not be null", + () -> cache.get(null, 1)); + } + + @Test + public void testGet() { + EnumCache cache = new EnumCache(); + + assertEquals(TestEnum.Alice, cache.get(TestEnum.class, -1)); + assertEquals(TestEnum.Bob, cache.get(TestEnum.class, 0)); + assertEquals(TestEnum.Charlie, cache.get(TestEnum.class, 1)); + + assertEquals(NotEnum.Alice, cache.get(NotEnum.class, -11)); + assertEquals(NotEnum.Bob, cache.get(NotEnum.class, 10)); + assertEquals(NotEnum.Charlie, cache.get(NotEnum.class, 11)); + } + + @Test + public void testGetInvalid() { + EnumCache cache = new EnumCache(); + + assertNull(cache.get(TestEnum.class, 42)); + assertNull(cache.get(NotEnum.class, 42)); + } +} diff --git a/lib/java/test/org/apache/thrift/partial/ExceptionAsserts.java b/lib/java/test/org/apache/thrift/partial/ExceptionAsserts.java new file mode 100644 index 000000000..239903cf4 --- /dev/null +++ b/lib/java/test/org/apache/thrift/partial/ExceptionAsserts.java @@ -0,0 +1,92 @@ +/* + * 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.thrift.partial; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public final class ExceptionAsserts { + private ExceptionAsserts() {} + + @FunctionalInterface + public interface CodeThatMayThrow { + void run() throws Exception; + } + + /** + * Asserts that the given code throws an exception of the given type + * and that the exception message contains the given sub-message. + * + * Usage: + * + * ExceptionAsserts.assertThrows( + * IllegalArgumentException.class, + * "'nullArg' must not be null", + * () -> Preconditions.checkNotNull(null, "nullArg")); + * + * Note: JUnit 5 has similar functionality but it will be a long time before + * we move to that framework because of significant differences and lack of + * backward compatibility for some JUnit rules. + */ + public static <E extends Exception> void assertThrows( + Class<E> expectedExceptionClass, + String partialMessage, + CodeThatMayThrow code) { + + Exception thrownException = null; + + try { + code.run(); + } catch (Exception e) { + if (expectedExceptionClass.isAssignableFrom(e.getClass())) { + + thrownException = e; + + if (partialMessage != null) { + String msg = e.getMessage(); + assertNotNull( + String.format("Exception message is null, expected to contain: '%s'", partialMessage), + msg); + assertTrue( + String.format("Exception message '%s' does not contain: '%s'", msg, partialMessage), + msg.contains(partialMessage)); + } + } else { + fail(String.format( + "Expected exception of type %s but got %s", + expectedExceptionClass.getName(), + e.getClass().getName())); + } + } + + if (thrownException == null) { + fail(String.format( + "Expected exception of type %s but got none", + expectedExceptionClass.getName())); + } + } + + public static <E extends Exception> void assertThrows( + Class<E> expectedExceptionClass, + CodeThatMayThrow code) { + assertThrows(expectedExceptionClass, null, code); + } +} diff --git a/lib/java/test/org/apache/thrift/partial/PartialThriftComparerTest.java b/lib/java/test/org/apache/thrift/partial/PartialThriftComparerTest.java new file mode 100644 index 000000000..e1209d733 --- /dev/null +++ b/lib/java/test/org/apache/thrift/partial/PartialThriftComparerTest.java @@ -0,0 +1,75 @@ +/* + * 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.thrift.partial; + +import static org.junit.Assert.*; + +import org.apache.thrift.TDeserializer; +import org.apache.thrift.TException; +import org.apache.thrift.partial.TestStruct; +import org.apache.thrift.partial.ThriftField; +import org.apache.thrift.protocol.TBinaryProtocol; +import org.apache.thrift.protocol.TCompactProtocol; +import org.junit.Test; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class PartialThriftComparerTest { + + private ThriftSerDe serde; + private PartialThriftTestData testData = new PartialThriftTestData(); + + public PartialThriftComparerTest() throws TException { + this.serde = new ThriftSerDe(); + } + + @Test + public void testCompareSimple() throws TException, IOException { + TestStruct ts1 = testData.createTestStruct(1, 1); + assertTrue(ts1.isSetI16Field()); + assertTrue(ts1.isSetI32Field()); + + byte[] bytesBinary = serde.serializeBinary(ts1); + byte[] bytesCompact = serde.serializeCompact(ts1); + + List<String> fieldNames = Arrays.asList("i32Field"); + TDeserializer partialBinaryDeser = + new TDeserializer(TestStruct.class, fieldNames, new TBinaryProtocol.Factory()); + TDeserializer partialCompactDeser = + new TDeserializer(TestStruct.class, fieldNames, new TCompactProtocol.Factory()); + + ThriftMetadata.ThriftStruct metadata = partialBinaryDeser.getMetadata(); + PartialThriftComparer comparer = new PartialThriftComparer(metadata); + + StringBuilder sb = new StringBuilder(); + TestStruct ts2 = (TestStruct) partialBinaryDeser.partialDeserializeObject(bytesBinary); + if (!comparer.areEqual(ts1, ts2, sb)) { + fail(sb.toString()); + } + + ts2 = (TestStruct) partialCompactDeser.partialDeserializeObject(bytesCompact); + if (!comparer.areEqual(ts1, ts2, sb)) { + fail(sb.toString()); + } + } +} diff --git a/lib/java/test/org/apache/thrift/partial/PartialThriftTestData.java b/lib/java/test/org/apache/thrift/partial/PartialThriftTestData.java new file mode 100644 index 000000000..6376075da --- /dev/null +++ b/lib/java/test/org/apache/thrift/partial/PartialThriftTestData.java @@ -0,0 +1,311 @@ +/* + * 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.thrift.partial; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Helpers for creating test data related to partial deserialization. + */ +public class PartialThriftTestData { + + public final byte[] BYTES = new byte[] { 1, 2, 3 }; + + public SmallStruct createSmallStruct(int id) { + return new SmallStruct() + .setByteField((byte) id) + .setI16Field((short) id) + .setI32Field(id) + .setI64Field(id) + .setDoubleField(id) + .setStringField(Integer.toString(id)) + .setEnumField(TstEnum.E_ONE); + } + + public TestStruct createTestStruct(int id, int numItems) { + + TestStruct ts = new TestStruct() + .setByteField((byte) id) + .setI16Field((short) id) + .setI32Field(id) + .setI64Field(id) + .setDoubleField(id) + .setStringField(Integer.toString(id)) + .setEnumField(TstEnum.E_ONE) + .setBinaryField(BYTES) + .setStructField(createSmallStruct(id)); + + initListFields(ts, id, numItems); + initSetFields(ts, id, numItems); + initMapFields(ts, id, numItems); + + return ts; + } + + public void initListFields(TestStruct ts, int id, int numItems) { + List<Byte> byteList = new ArrayList<>(numItems); + List<Short> i16List = new ArrayList<>(numItems); + List<Integer> i32List = new ArrayList<>(numItems); + List<Long> i64List = new ArrayList<>(numItems); + List<Double> doubleList = new ArrayList<>(numItems); + List<String> stringList = new ArrayList<>(numItems); + List<TstEnum> enumList = new ArrayList<>(numItems); + + List<List<Integer>> listList = new ArrayList<>(numItems); + List<Set<Integer>> setList = new ArrayList<>(numItems); + List<Map<String, Integer>> mapList = new ArrayList<>(numItems); + List<SmallStruct> structList = new ArrayList<>(numItems); + List<ByteBuffer> binaryList = new ArrayList<>(numItems); + + for (int i = 0; i < numItems; i++) { + byteList.add((byte) i); + i16List.add((short) i); + i32List.add(i); + i64List.add((long)i); + doubleList.add((double) i); + stringList.add(Integer.toString(i)); + enumList.add(TstEnum.E_ONE); + structList.add(createSmallStruct(i)); + binaryList.add(ByteBuffer.wrap(BYTES)); + + List<Integer> listItem = new ArrayList<>(numItems); + listList.add(listItem); + + Set<Integer> setItem = new HashSet<>(); + setList.add(setItem); + + Map<String, Integer> mapItem = new HashMap<>(); + mapList.add(mapItem); + + for (int j = 0; j < numItems; j++) { + listItem.add(j); + setItem.add(j); + mapItem.put(Integer.toString(j), j); + } + } + + ts.setByteList(byteList) + .setI16List(i16List) + .setI32List(i32List) + .setI64List(i64List) + .setDoubleList(doubleList) + .setStringList(stringList) + .setEnumList(enumList) + .setListList(listList) + .setSetList(setList) + .setMapList(mapList) + .setStructList(structList) + .setBinaryList(binaryList); + } + + public void initSetFields(TestStruct ts, int id, int numItems) { + Set<Byte> byteSet = new HashSet<>(); + Set<Short> i16Set = new HashSet<>(); + Set<Integer> i32Set = new HashSet<>(); + Set<Long> i64Set = new HashSet<>(); + Set<Double> doubleSet = new HashSet<>(); + Set<String> stringSet = new HashSet<>(); + Set<TstEnum> enumSet = new HashSet<>(); + + Set<List<Integer>> listSet = new HashSet<>(); + Set<Set<Integer>> setSet = new HashSet<>(); + Set<Map<String, Integer>> mapSet = new HashSet<>(); + Set<SmallStruct> structSet = new HashSet<>(); + Set<ByteBuffer> binarySet = new HashSet<>(); + + for (int i = 0; i < numItems; i++) { + byteSet.add((byte) i); + i16Set.add((short) i); + i32Set.add(i); + i64Set.add((long)i); + doubleSet.add((double) i); + stringSet.add(Integer.toString(i)); + enumSet.add(TstEnum.E_ONE); + structSet.add(createSmallStruct(i)); + binarySet.add(ByteBuffer.wrap(BYTES)); + + List<Integer> listItem = new ArrayList<>(numItems); + Set<Integer> setItem = new HashSet<>(); + Map<String, Integer> mapItem = new HashMap<>(); + + for (int j = 0; j < numItems; j++) { + setItem.add(j); + listItem.add(j); + mapItem.put(Integer.toString(j), j); + } + + listSet.add(listItem); + setSet.add(setItem); + mapSet.add(mapItem); + } + + ts.setByteSet(byteSet) + .setI16Set(i16Set) + .setI32Set(i32Set) + .setI64Set(i64Set) + .setDoubleSet(doubleSet) + .setStringSet(stringSet) + .setEnumSet(enumSet) + .setListSet(listSet) + .setSetSet(setSet) + .setMapSet(mapSet) + .setStructSet(structSet) + .setBinarySet(binarySet); + } + + public void initMapFields(TestStruct ts, int id, int numItems) { + Map<Byte, Byte> byteMap = new HashMap<>(); + Map<Short, Short> i16Map = new HashMap<>(); + Map<Integer, Integer> i32Map = new HashMap<>(); + Map<Long, Long> i64Map = new HashMap<>(); + Map<Double, Double> doubleMap = new HashMap<>(); + Map<String, String> stringMap = new HashMap<>(); + Map<TstEnum, TstEnum> enumMap = new HashMap<>(); + + Map<Integer, List<Integer>> listMap = new HashMap<>(); + Map<Integer, Set<Integer>> setMap = new HashMap<>(); + Map<Integer, Map<Integer, Integer>> mapMap = new HashMap<>(); + Map<SmallStruct, SmallStruct> structMap = new HashMap<>(); + Map<Integer, ByteBuffer> binaryMap = new HashMap<>(); + + for (int i = 0; i < numItems; i++) { + byteMap.put((byte) i, (byte) i); + i16Map.put((short) i, (short) i); + i32Map.put(i, i); + i64Map.put((long) i, (long) i); + doubleMap.put((double) i, (double) i); + stringMap.put(Integer.toString(i), Integer.toString(i)); + enumMap.put(TstEnum.E_ONE, TstEnum.E_ONE); + structMap.put(createSmallStruct(i), createSmallStruct(i)); + binaryMap.put(i, ByteBuffer.wrap(BYTES)); + + List<Integer> listItem = new ArrayList<>(numItems); + listMap.put(i, listItem); + + Set<Integer> setItem = new HashSet<>(); + setMap.put(i, setItem); + + Map<Integer, Integer> mapItem = new HashMap<>(); + mapMap.put(i, mapItem); + + for (int j = 0; j < numItems; j++) { + listItem.add(j); + setItem.add(j); + mapItem.put(j, j); + } + } + + ts.setByteMap(byteMap) + .setI16Map(i16Map) + .setI32Map(i32Map) + .setI64Map(i64Map) + .setDoubleMap(doubleMap) + .setStringMap(stringMap) + .setEnumMap(enumMap) + .setListMap(listMap) + .setSetMap(setMap) + .setMapMap(mapMap) + .setStructMap(structMap) + .setBinaryMap(binaryMap); + } + + public List<String> allFieldsOfTestStruct() { + return new ArrayList<>( + Arrays.asList( + "byteField", + "i16Field", + "i32Field", + "i64Field", + "doubleField", + "stringField", + "structField.byteField", + "structField.i16Field", + "structField.i32Field", + "structField.i64Field", + "structField.doubleField", + "structField.stringField", + "structField.enumField", + "enumField", + "binaryField", + "byteList", + "i16List", + "i32List", + "i64List", + "doubleList", + "stringList", + "enumList", + "listList", + "setList", + "mapList", + "structList.byteField", + "structList.i16Field", + "structList.i32Field", + "structList.i64Field", + "structList.doubleField", + "structList.stringField", + "structList.enumField", + "binaryList", + "byteSet", + "i16Set", + "i32Set", + "i64Set", + "doubleSet", + "stringSet", + "enumSet", + "listSet", + "setSet", + "mapSet", + "structSet.byteField", + "structSet.i16Field", + "structSet.i32Field", + "structSet.i64Field", + "structSet.doubleField", + "structSet.stringField", + "structSet.enumField", + "binarySet", + "byteMap", + "i16Map", + "i32Map", + "i64Map", + "doubleMap", + "stringMap", + "enumMap", + "listMap", + "setMap", + "mapMap", + "structMap.byteField", + "structMap.i16Field", + "structMap.i32Field", + "structMap.i64Field", + "structMap.doubleField", + "structMap.stringField", + "structMap.enumField", + "binaryMap" + ) + ); + } +} diff --git a/lib/java/test/org/apache/thrift/partial/TFieldDataTest.java b/lib/java/test/org/apache/thrift/partial/TFieldDataTest.java new file mode 100644 index 000000000..0a838e970 --- /dev/null +++ b/lib/java/test/org/apache/thrift/partial/TFieldDataTest.java @@ -0,0 +1,51 @@ +/* + * 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.thrift.partial; + +import org.apache.thrift.protocol.TField; +import org.apache.thrift.protocol.TType; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class TFieldDataTest { + + @Test + public void testEncodeStop() { + TField field = new TField("", TType.STOP, (short) 0); + int data = TFieldData.encode(TType.STOP); + + assertEquals(field.type, TFieldData.getType(data)); + assertEquals(field.id, TFieldData.getId(data)); + } + + @Test + public void testEncodeRest() { + for (byte type = 1; type <= 16; type++) { + for (short id = 0; id < Short.MAX_VALUE; id++) { + TField field = new TField("", type, id); + int data = TFieldData.encode(type, id); + + assertEquals(field.type, TFieldData.getType(data)); + assertEquals(field.id, TFieldData.getId(data)); + } + } + } +} diff --git a/lib/java/test/org/apache/thrift/partial/TestData.java b/lib/java/test/org/apache/thrift/partial/TestData.java new file mode 100644 index 000000000..1779346f7 --- /dev/null +++ b/lib/java/test/org/apache/thrift/partial/TestData.java @@ -0,0 +1,57 @@ +/* + * 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.thrift.partial; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Frequently used test data items. + */ +public final class TestData { + private TestData() {} + + + // Array data. + public static Object[] nullArray = null; + public static Object[] emptyArray = new Object[0]; + public static Object[] nonEmptyArray = new Object[1]; + + public static byte[] nullByteArray = null; + public static byte[] emptyByteArray = new byte[0]; + public static byte[] nonEmptyByteArray = new byte[1]; + + public static short[] nullShortArray = null; + public static short[] emptyShortArray = new short[0]; + public static short[] nonEmptyShortArray = new short[1]; + + public static int[] nullIntArray = null; + public static int[] emptyIntArray = new int[0]; + public static int[] nonEmptyIntArray = new int[1]; + + public static long[] nullLongArray = null; + public static long[] emptyLongArray = new long[0]; + public static long[] nonEmptyLongArray = new long[1]; + + public static List<Object> nullList = null; + public static List<Object> emptyList = new ArrayList<Object>(); + public static List<Object> validList = Arrays.asList(new Object[1]); +} diff --git a/lib/java/test/org/apache/thrift/partial/ThriftFieldTest.java b/lib/java/test/org/apache/thrift/partial/ThriftFieldTest.java new file mode 100644 index 000000000..a6d5655e4 --- /dev/null +++ b/lib/java/test/org/apache/thrift/partial/ThriftFieldTest.java @@ -0,0 +1,149 @@ +/* + * 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.thrift.partial; + +import static org.junit.Assert.*; + +import org.apache.thrift.partial.ExceptionAsserts; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.*; + +public class ThriftFieldTest { + + @Test + public void testArgChecks() { + ThriftField test; + List<ThriftField> testFields; + + // Should not throw. + test = new ThriftField("foo"); + test = new ThriftField("foo", Arrays.asList(new ThriftField("bar"))); + testFields = ThriftField.fromNames(Arrays.asList("foo")); + + // Verify it throws. + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'name' must not be null", + () -> new ThriftField(null, Collections.emptyList())); + + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'fields' must not be null", + () -> new ThriftField("foo", null)); + + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'fieldNames' must not be null", + () -> ThriftField.fromNames(null)); + + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'fieldNames' must have at least one element", + () -> ThriftField.fromNames(Collections.emptyList())); + } + + @Test + public void testFromNames() { + List<String> fieldNames = Arrays.asList( + "f1", + "f2.f21", + "f3.f31.f311", + "f3.f32.f321", + "f3.f32.f322" + ); + + List<ThriftField> testFields = ThriftField.fromNames(fieldNames); + + assertEquals(3, testFields.size()); + ThriftField f1 = testFields.get(0); + ThriftField f2 = testFields.get(1); + ThriftField f3 = testFields.get(2); + assertEquals("f1", f1.name); + assertEquals("f2", f2.name); + assertEquals("f3", f3.name); + + assertEquals(0, f1.fields.size()); + assertEquals(1, f2.fields.size()); + assertEquals(2, f3.fields.size()); + + ThriftField f21 = f2.fields.get(0); + ThriftField f31 = f3.fields.get(0); + ThriftField f32 = f3.fields.get(1); + assertEquals("f21", f21.name); + assertEquals("f31", f31.name); + assertEquals("f32", f32.name); + + assertEquals(0, f21.fields.size()); + assertEquals(1, f31.fields.size()); + assertEquals(2, f32.fields.size()); + + ThriftField f311 = f31.fields.get(0); + ThriftField f321 = f32.fields.get(0); + ThriftField f322 = f32.fields.get(1); + assertEquals("f311", f311.name); + assertEquals("f321", f321.name); + assertEquals("f322", f322.name); + + assertEquals(0, f311.fields.size()); + assertEquals(0, f321.fields.size()); + assertEquals(0, f322.fields.size()); + } + + @Test + public void testEquality() { + List<String> fieldNames = Arrays.asList( + "f1", + "f2.f21", + "f3.f31.f311", + "f3.f32.f321", + "f3.f32.f322" + ); + + List<ThriftField> testFields = ThriftField.fromNames(fieldNames); + List<ThriftField> testFields2 = testFields; + + assertSame(testFields, testFields2); + assertEquals(testFields, testFields2); + + List<ThriftField> testFields3 = ThriftField.fromNames(fieldNames); + assertNotSame(testFields, testFields3); + assertEquals(testFields, testFields3); + assertEquals(testFields.hashCode(), testFields3.hashCode()); + + List<String> fieldNamesDiff = Arrays.asList( + "f1", + "f2.f21", + "f3.f31.f311", + "f3.f32.f323", + "f3.f32.f322" + ); + + List<ThriftField> testFields4 = ThriftField.fromNames(fieldNamesDiff); + assertNotSame(testFields, testFields4); + assertNotEquals(testFields, testFields4); + assertNotEquals(testFields.hashCode(), testFields4.hashCode()); + } +} diff --git a/lib/java/test/org/apache/thrift/partial/ThriftMetadataTest.java b/lib/java/test/org/apache/thrift/partial/ThriftMetadataTest.java new file mode 100644 index 000000000..acc53c8a6 --- /dev/null +++ b/lib/java/test/org/apache/thrift/partial/ThriftMetadataTest.java @@ -0,0 +1,294 @@ +/* + * 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.thrift.partial; + +import static org.junit.Assert.*; + +import org.apache.thrift.partial.TestStruct; +import org.apache.thrift.partial.ThriftField; +import org.apache.thrift.partial.ExceptionAsserts; + +import org.apache.thrift.TBase; +import org.apache.thrift.meta_data.EnumMetaData; +import org.apache.thrift.meta_data.FieldValueMetaData; +import org.apache.thrift.meta_data.ListMetaData; +import org.apache.thrift.meta_data.MapMetaData; +import org.apache.thrift.meta_data.SetMetaData; +import org.apache.thrift.meta_data.StructMetaData; +import org.apache.thrift.protocol.TType; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + +public class ThriftMetadataTest { + + private PartialThriftTestData testData = new PartialThriftTestData(); + + @Test + public void testArgChecks() { + // Should not throw. + List<ThriftField> testFields = ThriftField.fromNames(Arrays.asList("byteField")); + ThriftMetadata.ThriftStruct.fromFields(TestStruct.class, testFields); + + // Verify it throws correctly. + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'clasz' must not be null", + () -> ThriftMetadata.ThriftStruct.fromFields(null, testFields)); + + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'fields' must not be null", + () -> ThriftMetadata.ThriftStruct.fromFields(TestStruct.class, null)); + } + + @Test + public void testThriftStructOf() { + ThriftMetadata.ThriftStruct testStruct = ThriftMetadata.ThriftStruct.of(TestStruct.class); + assertEquals(45, testStruct.fields.keySet().size()); + validateFieldMetadata(testStruct, 1, "byteField", TType.BYTE); + validateFieldMetadata(testStruct, 2, "i16Field", TType.I16); + validateFieldMetadata(testStruct, 3, "i32Field", TType.I32); + validateFieldMetadata(testStruct, 4, "i64Field", TType.I64); + validateFieldMetadata(testStruct, 5, "doubleField", TType.DOUBLE); + validateFieldMetadata(testStruct, 6, "stringField", TType.STRING); + validateFieldMetadata(testStruct, 7, "enumField", TType.ENUM); + validateFieldMetadata(testStruct, 8, "binaryField", TType.STRING); + + validateListFieldMetadata(testStruct, 10, "byteList", TType.BYTE); + validateSetFieldMetadata(testStruct, 35, "stringSet", TType.STRING); + validateMapFieldMetadata(testStruct, 61, "binaryMap", TType.I32, TType.STRING); + } + + @Test + public void testUnion() { + ThriftMetadata.ThriftStruct structWithUnions = + ThriftMetadata.ThriftStruct.of(StructWithUnions.class); + validateBasicFieldMetadata(structWithUnions, StructWithUnions.class, 1, "intValue"); + validateBasicFieldMetadata(structWithUnions, StructWithUnions.class, 2, "smallStruct"); + validateBasicFieldMetadata(structWithUnions, StructWithUnions.class, 3, "simpleUnion"); + validateBasicFieldMetadata(structWithUnions, StructWithUnions.class, 4, "unionList"); + validateBasicFieldMetadata(structWithUnions, StructWithUnions.class, 5, "unionSet"); + validateBasicFieldMetadata(structWithUnions, StructWithUnions.class, 6, "keyUnionMap"); + validateBasicFieldMetadata(structWithUnions, StructWithUnions.class, 7, "valUnionMap"); + validateBasicFieldMetadata(structWithUnions, StructWithUnions.class, 8, "unionMap"); + + ThriftMetadata.ThriftStructBase smallStructMetadata = + (ThriftMetadata.ThriftStructBase) structWithUnions.fields.get(2); + assertFalse(smallStructMetadata.isUnion()); + + ThriftMetadata.ThriftStructBase simpleUnionMetadata = + (ThriftMetadata.ThriftStructBase) structWithUnions.fields.get(3); + assertTrue(simpleUnionMetadata.isUnion()); + + ThriftMetadata.ThriftList unionListMetadata = + (ThriftMetadata.ThriftList) structWithUnions.fields.get(4); + assertTrue(unionListMetadata.hasUnion()); + + ThriftMetadata.ThriftSet unionSetMetadata = + (ThriftMetadata.ThriftSet) structWithUnions.fields.get(5); + assertTrue(unionSetMetadata.hasUnion()); + + ThriftMetadata.ThriftMap keyUnionMapMetadata = + (ThriftMetadata.ThriftMap) structWithUnions.fields.get(6); + assertTrue(keyUnionMapMetadata.hasUnion()); + + ThriftMetadata.ThriftMap valUnionMapMetadata = + (ThriftMetadata.ThriftMap) structWithUnions.fields.get(7); + assertTrue(valUnionMapMetadata.hasUnion()); + + ThriftMetadata.ThriftMap unionMapMetadata = + (ThriftMetadata.ThriftMap) structWithUnions.fields.get(8); + assertTrue(unionMapMetadata.hasUnion()); + } + + private ThriftMetadata.ThriftObject validateBasicFieldMetadata( + ThriftMetadata.ThriftStruct testStruct, + int id, + String fieldName) { + return validateBasicFieldMetadata(testStruct, TestStruct.class, id, fieldName); + } + + private ThriftMetadata.ThriftObject validateBasicFieldMetadata( + ThriftMetadata.ThriftStruct testStruct, + Class<? extends TBase> clazz, + int id, + String fieldName) { + + assertNotNull(testStruct); + assertNull(testStruct.parent); + assertEquals(clazz, ((StructMetaData) testStruct.data.valueMetaData).structClass); + assertTrue(testStruct.fields.containsKey(id)); + + ThriftMetadata.ThriftObject fieldMetadata = + (ThriftMetadata.ThriftObject) testStruct.fields.get(id); + assertEquals(testStruct, fieldMetadata.parent); + + assertEquals(id, fieldMetadata.fieldId.getThriftFieldId()); + assertEquals(fieldName, fieldMetadata.fieldId.getFieldName()); + assertEquals(fieldName, fieldMetadata.data.fieldName); + + assertEquals("root ==> " + fieldName, fieldMetadata.toString()); + + return fieldMetadata; + } + + private void validateBasicFieldValueMetadata( + ThriftMetadata.ThriftObject fieldMetadata, + String fieldName, + byte ttype) { + + assertEquals(ttype, fieldMetadata.data.valueMetaData.type); + assertEquals(getMetaDataClassForTType(ttype), fieldMetadata.data.valueMetaData.getClass()); + Class<? extends ThriftMetadata.ThriftObject> fieldMetadataClass = getClassForTType(ttype); + assertEquals(fieldMetadataClass, fieldMetadata.getClass()); + if (fieldMetadataClass == ThriftMetadata.ThriftPrimitive.class) { + ThriftMetadata.ThriftPrimitive primitive + = (ThriftMetadata.ThriftPrimitive) fieldMetadata; + if (fieldName.startsWith("binary") && (ttype == TType.STRING)) { + assertTrue(primitive.isBinary()); + } else { + assertFalse(primitive.isBinary()); + } + } + } + + private void validateFieldMetadata( + ThriftMetadata.ThriftStruct testStruct, + int id, + String fieldName, + byte ttype) { + + ThriftMetadata.ThriftObject fieldMetadata = + validateBasicFieldMetadata(testStruct, id, fieldName); + validateBasicFieldValueMetadata(fieldMetadata, fieldName, ttype); + } + + private void validateListFieldMetadata( + ThriftMetadata.ThriftStruct testStruct, + int id, + String fieldName, + byte ttype) { + + ThriftMetadata.ThriftObject fieldMetadata = + validateBasicFieldMetadata(testStruct, id, fieldName); + validateBasicFieldValueMetadata(fieldMetadata, fieldName, TType.LIST); + + ThriftMetadata.ThriftList thriftList = (ThriftMetadata.ThriftList) fieldMetadata; + ThriftMetadata.ThriftObject elementMetadata = thriftList.elementData; + validateBasicFieldValueMetadata(elementMetadata, fieldName + "_element", ttype); + } + + private void validateSetFieldMetadata( + ThriftMetadata.ThriftStruct testStruct, + int id, + String fieldName, + byte ttype) { + + ThriftMetadata.ThriftObject fieldMetadata = + validateBasicFieldMetadata(testStruct, id, fieldName); + validateBasicFieldValueMetadata(fieldMetadata, fieldName, TType.SET); + + ThriftMetadata.ThriftSet thriftSet = (ThriftMetadata.ThriftSet) fieldMetadata; + ThriftMetadata.ThriftObject elementMetadata = thriftSet.elementData; + validateBasicFieldValueMetadata(elementMetadata, fieldName + "_element", ttype); + } + + private void validateMapFieldMetadata( + ThriftMetadata.ThriftStruct testStruct, + int id, + String fieldName, + byte keyType, + byte valueType) { + + ThriftMetadata.ThriftObject fieldMetadata = + validateBasicFieldMetadata(testStruct, id, fieldName); + validateBasicFieldValueMetadata(fieldMetadata, fieldName, TType.MAP); + + ThriftMetadata.ThriftMap thriftMap = (ThriftMetadata.ThriftMap) fieldMetadata; + ThriftMetadata.ThriftObject keyMetadata = thriftMap.keyData; + ThriftMetadata.ThriftObject valueMetadata = thriftMap.valueData; + validateBasicFieldValueMetadata(keyMetadata, fieldName + "_key", keyType); + validateBasicFieldValueMetadata(valueMetadata, fieldName + "_value", valueType); + } + + private Class<? extends FieldValueMetaData> getMetaDataClassForTType(byte ttype) { + switch (ttype) { + case TType.STRUCT: + return StructMetaData.class; + + case TType.LIST: + return ListMetaData.class; + + case TType.MAP: + return MapMetaData.class; + + case TType.SET: + return SetMetaData.class; + + case TType.ENUM: + return EnumMetaData.class; + + case TType.BOOL: + case TType.BYTE: + case TType.I16: + case TType.I32: + case TType.I64: + case TType.DOUBLE: + case TType.STRING: + return FieldValueMetaData.class; + + default: + throw ThriftMetadata.unsupportedFieldTypeException(ttype); + } + } + + private Class<? extends ThriftMetadata.ThriftObject> getClassForTType(byte ttype) { + switch (ttype) { + case TType.STRUCT: + return ThriftMetadata.ThriftStruct.class; + + case TType.LIST: + return ThriftMetadata.ThriftList.class; + + case TType.MAP: + return ThriftMetadata.ThriftMap.class; + + case TType.SET: + return ThriftMetadata.ThriftSet.class; + + case TType.ENUM: + return ThriftMetadata.ThriftEnum.class; + + case TType.BOOL: + case TType.BYTE: + case TType.I16: + case TType.I32: + case TType.I64: + case TType.DOUBLE: + case TType.STRING: + return ThriftMetadata.ThriftPrimitive.class; + + default: + throw ThriftMetadata.unsupportedFieldTypeException(ttype); + } + } +} diff --git a/lib/java/test/org/apache/thrift/partial/ThriftSerDe.java b/lib/java/test/org/apache/thrift/partial/ThriftSerDe.java new file mode 100644 index 000000000..361c32c60 --- /dev/null +++ b/lib/java/test/org/apache/thrift/partial/ThriftSerDe.java @@ -0,0 +1,71 @@ +/* + * 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.thrift.partial; + +import org.apache.thrift.TBase; +import org.apache.thrift.TDeserializer; +import org.apache.thrift.TException; +import org.apache.thrift.TSerializer; +import org.apache.thrift.protocol.TBinaryProtocol; +import org.apache.thrift.protocol.TCompactProtocol; + +public class ThriftSerDe { + private TSerializer binarySerializer; + private TSerializer compactSerializer; + private TDeserializer binaryDeserializer; + private TDeserializer compactDeserializer; + + public ThriftSerDe() throws TException { + this.binarySerializer = new TSerializer(new TBinaryProtocol.Factory()); + this.compactSerializer = new TSerializer(new TCompactProtocol.Factory()); + this.binaryDeserializer = new TDeserializer(new TBinaryProtocol.Factory()); + this.compactDeserializer = new TDeserializer(new TCompactProtocol.Factory()); + } + + public byte[] serializeBinary(TBase obj) throws TException { + return binarySerializer.serialize(obj); + } + + public byte[] serializeCompact(TBase obj) throws TException { + return compactSerializer.serialize(obj); + } + + public <T extends TBase> T deserializeBinary(byte[] bytes, Class<T> clazz) throws TException { + T instance = this.newInstance(clazz); + binaryDeserializer.deserialize(instance, bytes); + return clazz.cast(instance); + } + + public <T extends TBase> T deserializeCompact(byte[] bytes, Class<T> clazz) throws TException { + T instance = this.newInstance(clazz); + compactDeserializer.deserialize(instance, bytes); + return clazz.cast(instance); + } + + private <T extends TBase> T newInstance(Class<T> clazz) { + T instance = null; + try { + instance = clazz.newInstance(); + } catch (InstantiationException e) { + } catch (IllegalAccessException e) { + } + return clazz.cast(instance); + } +} diff --git a/lib/java/test/org/apache/thrift/partial/ThriftStructProcessorTest.java b/lib/java/test/org/apache/thrift/partial/ThriftStructProcessorTest.java new file mode 100644 index 000000000..d4ab92509 --- /dev/null +++ b/lib/java/test/org/apache/thrift/partial/ThriftStructProcessorTest.java @@ -0,0 +1,315 @@ +/* + * 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.thrift.partial; + +import static org.junit.Assert.*; + +import org.apache.thrift.partial.TestStruct; +import org.apache.thrift.partial.ThriftField; +import org.apache.thrift.partial.ThriftMetadata; +import org.apache.thrift.partial.TstEnum; + +import org.apache.thrift.TBase; +import org.apache.thrift.TException; +import org.apache.thrift.TFieldIdEnum; +import org.junit.Test; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class ThriftStructProcessorTest { + + private PartialThriftTestData testData = new PartialThriftTestData(); + + @Test + public void testStruct() throws TException { + List<ThriftField> fields = ThriftField.fromNames(Arrays.asList("i32Field")); + ThriftMetadata.ThriftStruct metadata = + ThriftMetadata.ThriftStruct.fromFields(TestStruct.class, fields); + ThriftStructProcessor processor = new ThriftStructProcessor(); + Object instance = processor.createNewStruct(metadata); + assertNotNull(instance); + assertTrue(instance instanceof TBase); + assertTrue(instance instanceof TestStruct); + + Object instance2 = processor.prepareStruct(instance); + assertSame(instance, instance2); + } + + @Test + public void testList() throws TException { + final int numItems = 10; + ThriftStructProcessor processor = new ThriftStructProcessor(); + Object instance = processor.createNewList(numItems); + assertNotNull(instance); + assertTrue(instance instanceof Object[]); + + Object[] items = (Object[]) instance; + for (int i = 0; i < numItems; i++) { + assertNull(items[i]); + processor.setListElement(instance, i, Integer.valueOf(i)); + assertEquals(i, items[i]); + } + + assertTrue(processor.prepareList(instance) instanceof List<?>); + } + + @Test + public void testMap() throws TException { + final int numItems = 10; + ThriftStructProcessor processor = new ThriftStructProcessor(); + Object instance = processor.createNewMap(numItems); + assertNotNull(instance); + assertTrue(instance instanceof Map<?, ?>); + + Map<Object, Object> items = (Map<Object, Object>) instance; + int ignoredIndex = -1; + for (int i = 0; i < numItems; i++) { + assertNull(items.get(i)); + processor.setMapElement(instance, ignoredIndex, Integer.valueOf(i), Integer.valueOf(i)); + assertEquals(i, items.get(i)); + } + + assertTrue(processor.prepareMap(instance) instanceof Map<?, ?>); + } + + @Test + public void testSet() throws TException { + final int numItems = 10; + ThriftStructProcessor processor = new ThriftStructProcessor(); + Object instance = processor.createNewSet(numItems); + assertNotNull(instance); + assertTrue(instance instanceof HashSet<?>); + + Set<?> items = (HashSet<?>) instance; + int ignoredIndex = -1; + + for (int i = 0; i < numItems; i++) { + assertFalse(items.contains(i)); + processor.setSetElement(instance, ignoredIndex, Integer.valueOf(i)); + assertTrue(items.contains(i)); + } + + assertTrue(processor.prepareSet(instance) instanceof Set<?>); + } + + @Test + public void testPrepareEnum() throws TException { + ThriftStructProcessor processor = new ThriftStructProcessor(); + Object instance = processor.prepareEnum(TstEnum.class, 1); + assertNotNull(instance); + assertEquals(TstEnum.E_ONE, instance); + + instance = processor.prepareEnum(TstEnum.class, 2); + assertNotNull(instance); + assertEquals(TstEnum.E_TWO, instance); + } + + @Test + public void testPrepareString() throws TException { + ThriftStructProcessor processor = new ThriftStructProcessor(); + ByteBuffer emptyBuffer = ByteBuffer.wrap(new byte[0]); + Object instance = processor.prepareString(emptyBuffer); + assertNotNull(instance); + assertTrue(instance instanceof String); + assertEquals("", instance); + + String value = "Hello world!"; + ByteBuffer buffer = ByteBuffer.wrap(value.getBytes(StandardCharsets.UTF_8)); + instance = processor.prepareString(buffer); + assertNotNull(instance); + assertTrue(instance instanceof String); + assertEquals(value, instance); + } + + @Test + public void testPrepareBinary() throws TException { + ThriftStructProcessor processor = new ThriftStructProcessor(); + ByteBuffer emptyBuffer = ByteBuffer.wrap(new byte[0]); + Object instance = processor.prepareBinary(emptyBuffer); + assertNotNull(instance); + assertTrue(instance instanceof ByteBuffer); + assertSame(emptyBuffer, instance); + } + + @Test + public void testStructPrimitiveFields() throws TException { + List<ThriftField> fields = ThriftField.fromNames( + Arrays.asList( + "byteField", + "i16Field", + "i32Field", + "i64Field", + "doubleField", + "stringField", + + "enumField", + "binaryField" + )); + + ThriftMetadata.ThriftStruct metadata = + ThriftMetadata.ThriftStruct.fromFields(TestStruct.class, fields); + ThriftStructProcessor processor = new ThriftStructProcessor(); + Object instance = processor.createNewStruct(metadata); + assertNotNull(instance); + assertTrue(instance instanceof TBase); + assertTrue(instance instanceof TestStruct); + + TestStruct struct = (TestStruct) instance; + + // byte + TFieldIdEnum fieldId = findFieldId(metadata, "byteField"); + assertNull(getFieldValue(struct, fieldId)); + processor.setByte(struct, fieldId, (byte) 42); + assertEquals(42, struct.getByteField()); + + // short + fieldId = findFieldId(metadata, "i16Field"); + assertNull(getFieldValue(struct, fieldId)); + processor.setInt16(struct, fieldId, (short) 42); + assertEquals(42, struct.getI16Field()); + + // int + fieldId = findFieldId(metadata, "i32Field"); + assertNull(getFieldValue(struct, fieldId)); + processor.setInt32(struct, fieldId, 42); + assertEquals(42, struct.getI32Field()); + + // long + fieldId = findFieldId(metadata, "i64Field"); + assertNull(getFieldValue(struct, fieldId)); + processor.setInt64(struct, fieldId, 42L); + assertEquals(42, struct.getI64Field()); + + // binary + fieldId = findFieldId(metadata, "binaryField"); + assertNull(getFieldValue(struct, fieldId)); + byte[] noBytes = new byte[0]; + ByteBuffer emptyBuffer = ByteBuffer.wrap(noBytes); + processor.setBinary(struct, fieldId, emptyBuffer); + assertArrayEquals(noBytes, struct.getBinaryField()); + + // string + fieldId = findFieldId(metadata, "stringField"); + assertNull(getFieldValue(struct, fieldId)); + String value = "Hello world!"; + ByteBuffer buffer = ByteBuffer.wrap(value.getBytes(StandardCharsets.UTF_8)); + processor.setString(struct, fieldId, buffer); + assertEquals(value, struct.getStringField()); + + // enum + fieldId = findFieldId(metadata, "enumField"); + assertNull(getFieldValue(struct, fieldId)); + TstEnum e1 = TstEnum.E_ONE; + processor.setEnumField(struct, fieldId, e1); + assertEquals(TstEnum.E_ONE, struct.getEnumField()); + } + + @Test + public void testStructContainerFields() throws TException { + List<ThriftField> fields = ThriftField.fromNames( + Arrays.asList( + // List field + "i32List", + + // Set field + "stringSet", + + // Map field + "stringMap", + + // Struct field + "structField" + )); + + ThriftMetadata.ThriftStruct metadata = + ThriftMetadata.ThriftStruct.fromFields(TestStruct.class, fields); + ThriftStructProcessor processor = new ThriftStructProcessor(); + Object instance = processor.createNewStruct(metadata); + assertNotNull(instance); + assertTrue(instance instanceof TBase); + assertTrue(instance instanceof TestStruct); + + TestStruct struct = (TestStruct) instance; + + // list + TFieldIdEnum fieldId = findFieldId(metadata, "i32List"); + assertNull(getFieldValue(struct, fieldId)); + Integer[] ints = new Integer[] { 1, 2, 3 }; + List<Integer> intList = Arrays.asList(ints); + processor.setListField(struct, fieldId, intList); + assertArrayEquals(ints, struct.getI32List().toArray()); + + // set + fieldId = findFieldId(metadata, "stringSet"); + assertNull(getFieldValue(struct, fieldId)); + String[] strings = new String[] { "Hello", "World!" }; + Set<String> stringSet = new HashSet<>(Arrays.asList(strings)); + processor.setSetField(struct, fieldId, stringSet); + assertEquals(stringSet, struct.getStringSet()); + + // map + fieldId = findFieldId(metadata, "stringMap"); + assertNull(getFieldValue(struct, fieldId)); + Map<String, String> stringMap = new HashMap<>(); + stringMap.put("foo", "bar"); + stringMap.put("Hello", "World!"); + processor.setMapField(struct, fieldId, stringMap); + assertEquals(stringMap, struct.getStringMap()); + + // struct + fieldId = findFieldId(metadata, "structField"); + assertNull(getFieldValue(struct, fieldId)); + SmallStruct smallStruct = new SmallStruct(); + smallStruct.setI32Field(42); + SmallStruct smallStruct2 = new SmallStruct(); + smallStruct2.setI32Field(42); + processor.setStructField(struct, fieldId, smallStruct); + assertEquals(smallStruct2, struct.getStructField()); + } + + private TFieldIdEnum findFieldId(ThriftMetadata.ThriftStruct metadata, String fieldName) { + Collection<ThriftMetadata.ThriftObject> fields = metadata.fields.values(); + for (ThriftMetadata.ThriftObject field : fields) { + if (fieldName.equalsIgnoreCase(field.fieldId.getFieldName())) { + return field.fieldId; + } + } + + fail("Field not found: " + fieldName); + return null; + } + + private Object getFieldValue(TBase struct, TFieldIdEnum fieldId) { + TFieldIdEnum fieldRef = struct.fieldForId(fieldId.getThriftFieldId()); + if (struct.isSet(fieldRef)) { + return struct.getFieldValue(fieldRef); + } else { + return null; + } + } +} diff --git a/lib/java/test/org/apache/thrift/partial/ValidateTest.java b/lib/java/test/org/apache/thrift/partial/ValidateTest.java new file mode 100644 index 000000000..9d96844f8 --- /dev/null +++ b/lib/java/test/org/apache/thrift/partial/ValidateTest.java @@ -0,0 +1,325 @@ +/* + * 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.thrift.partial; + +import org.apache.thrift.partial.ExceptionAsserts; +import org.apache.thrift.partial.TestData; + +import org.junit.Test; +import org.junit.runners.JUnit4; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; + +public class ValidateTest { + @Test + public void testCheckNotNull() { + String nonNullArg = "nonNullArg"; + String nullArg = null; + + // Should not throw. + Validate.checkNotNull(nonNullArg, "nonNullArg"); + + // Verify it throws. + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'nullArg' must not be null", + () -> Validate.checkNotNull(nullArg, "nullArg")); + } + + @Test + public void testCheckPositiveInteger() { + int positiveArg = 1; + int zero = 0; + int negativeArg = -1; + + // Should not throw. + Validate.checkPositiveInteger(positiveArg, "positiveArg"); + + // Verify it throws. + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'negativeArg' must be a positive integer", + () -> Validate.checkPositiveInteger(negativeArg, "negativeArg")); + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'zero' must be a positive integer", + () -> Validate.checkPositiveInteger(zero, "zero")); + } + + @Test + public void testCheckNotNegative() { + int positiveArg = 1; + int zero = 0; + int negativeArg = -1; + + // Should not throw. + Validate.checkNotNegative(zero, "zeroArg"); + Validate.checkNotNegative(positiveArg, "positiveArg"); + + // Verify it throws. + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'negativeArg' must not be negative", + () -> Validate.checkNotNegative(negativeArg, "negativeArg")); + } + + @Test + public void testCheckRequired() { + // Should not throw. + Validate.checkRequired(true, "arg"); + + // Verify it throws. + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'arg' is required", + () -> Validate.checkRequired(false, "arg")); + } + + @Test + public void testCheckValid() { + // Should not throw. + Validate.checkValid(true, "arg"); + + // Verify it throws. + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'arg' is invalid", + () -> Validate.checkValid(false, "arg")); + } + + @Test + public void testCheckValidWithValues() { + String validValues = "foo, bar"; + + // Should not throw. + Validate.checkValid(true, "arg", validValues); + + // Verify it throws. + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'arg' is invalid. Valid values are: foo, bar", + () -> Validate.checkValid(false, "arg", validValues)); + } + + @Test + public void testCheckNotNullAndNotEmpty() { + // Should not throw. + Validate.checkNotNullAndNotEmpty(TestData.nonEmptyArray, "array"); + Validate.checkNotNullAndNotEmpty(TestData.nonEmptyByteArray, "array"); + Validate.checkNotNullAndNotEmpty(TestData.nonEmptyShortArray, "array"); + Validate.checkNotNullAndNotEmpty(TestData.nonEmptyIntArray, "array"); + Validate.checkNotNullAndNotEmpty(TestData.nonEmptyLongArray, "array"); + + // Verify it throws. + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'string' must not be empty", + () -> Validate.checkNotNullAndNotEmpty("", "string")); + + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'array' must not be null", + () -> Validate.checkNotNullAndNotEmpty(TestData.nullArray, "array")); + + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'array' must have at least one element", + () -> Validate.checkNotNullAndNotEmpty(TestData.emptyArray, "array")); + + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'array' must not be null", + () -> Validate.checkNotNullAndNotEmpty(TestData.nullByteArray, "array")); + + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'array' must have at least one element", + () -> Validate.checkNotNullAndNotEmpty(TestData.emptyByteArray, "array")); + + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'array' must not be null", + () -> Validate.checkNotNullAndNotEmpty(TestData.nullShortArray, "array")); + + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'array' must have at least one element", + () -> Validate.checkNotNullAndNotEmpty(TestData.emptyShortArray, "array")); + + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'array' must not be null", + () -> Validate.checkNotNullAndNotEmpty(TestData.nullIntArray, "array")); + + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'array' must have at least one element", + () -> Validate.checkNotNullAndNotEmpty(TestData.emptyIntArray, "array")); + + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'array' must not be null", + () -> Validate.checkNotNullAndNotEmpty(TestData.nullLongArray, "array")); + + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'array' must have at least one element", + () -> Validate.checkNotNullAndNotEmpty(TestData.emptyLongArray, "array")); + } + + @Test + public void testCheckListNotNullAndNotEmpty() { + // Should not throw. + Validate.checkNotNullAndNotEmpty(TestData.validList, "list"); + + // Verify it throws. + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'list' must not be null", + () -> Validate.checkNotNullAndNotEmpty(TestData.nullList, "list")); + + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'list' must have at least one element", + () -> Validate.checkNotNullAndNotEmpty(TestData.emptyList, "list")); + } + + @Test + public void testCheckNotNullAndNumberOfElements() { + // Should not throw. + Validate.checkNotNullAndNumberOfElements(Arrays.asList(1, 2, 3), 3, "arg"); + + // Verify it throws. + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'arg' must not be null", + () -> Validate.checkNotNullAndNumberOfElements(null, 3, "arg") + ); + + // Verify it throws. + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "Number of elements in 'arg' must be exactly 3, 2 given.", + () -> Validate.checkNotNullAndNumberOfElements(Arrays.asList(1, 2), 3, "arg") + ); + } + + @Test + public void testCheckValuesEqual() { + // Should not throw. + Validate.checkValuesEqual(1, "arg1", 1, "arg2"); + + // Verify it throws. + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'arg1' (1) must equal 'arg2' (2)", + () -> Validate.checkValuesEqual(1, "arg1", 2, "arg2")); + } + + @Test + public void testCheckIntegerMultiple() { + // Should not throw. + Validate.checkIntegerMultiple(10, "arg1", 5, "arg2"); + + // Verify it throws. + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'arg1' (10) must be an integer multiple of 'arg2' (3)", + () -> Validate.checkIntegerMultiple(10, "arg1", 3, "arg2")); + } + + @Test + public void testCheckGreater() { + // Should not throw. + Validate.checkGreater(10, "arg1", 5, "arg2"); + + // Verify it throws. + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'arg1' (5) must be greater than 'arg2' (10)", + () -> Validate.checkGreater(5, "arg1", 10, "arg2")); + } + + @Test + public void testCheckGreaterOrEqual() { + // Should not throw. + Validate.checkGreaterOrEqual(10, "arg1", 5, "arg2"); + + // Verify it throws. + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'arg1' (5) must be greater than or equal to 'arg2' (10)", + () -> Validate.checkGreaterOrEqual(5, "arg1", 10, "arg2")); + } + + @Test + public void testCheckWithinRange() { + // Should not throw. + Validate.checkWithinRange(10, "arg", 5, 15); + Validate.checkWithinRange(10.0, "arg", 5.0, 15.0); + + // Verify it throws. + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'arg' (5) must be within the range [10, 20]", + () -> Validate.checkWithinRange(5, "arg", 10, 20)); + + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'arg' (5.0) must be within the range [10.0, 20.0]", + () -> Validate.checkWithinRange(5.0, "arg", 10.0, 20.0)); + } + + @Test + public void testCheckPathExists() throws IOException { + Path tempFile = Files.createTempFile("foo", "bar"); + Path tempDir = tempFile.getParent(); + Path notFound = Paths.get("<not-found>"); + + // Should not throw. + Validate.checkPathExists(tempFile, "tempFile"); + Validate.checkPathExists(tempDir, "tempDir"); + + // Verify it throws. + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "'nullArg' must not be null", + () -> Validate.checkPathExists(null, "nullArg")); + + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "Path notFound (<not-found>) does not exist", + () -> Validate.checkPathExists(notFound, "notFound")); + + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "must point to a directory", + () -> Validate.checkPathExistsAsDir(tempFile, "tempFile")); + + ExceptionAsserts.assertThrows( + IllegalArgumentException.class, + "must point to a file", + () -> Validate.checkPathExistsAsFile(tempDir, "tempDir")); + } +} |