| /* |
| * 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.beam.sdk.options; |
| |
| import static org.apache.beam.sdk.transforms.display.DisplayDataMatchers.hasDisplayItem; |
| import static org.apache.beam.sdk.transforms.display.DisplayDataMatchers.hasKey; |
| import static org.apache.beam.sdk.transforms.display.DisplayDataMatchers.hasNamespace; |
| import static org.apache.beam.sdk.transforms.display.DisplayDataMatchers.hasType; |
| import static org.apache.beam.sdk.transforms.display.DisplayDataMatchers.hasValue; |
| import static org.hamcrest.Matchers.allOf; |
| import static org.hamcrest.Matchers.hasItem; |
| import static org.hamcrest.Matchers.not; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertThat; |
| import static org.junit.Assert.assertTrue; |
| |
| import com.fasterxml.jackson.annotation.JsonIgnore; |
| import com.fasterxml.jackson.annotation.JsonProperty; |
| import com.fasterxml.jackson.annotation.JsonValue; |
| import com.fasterxml.jackson.databind.JsonMappingException; |
| import com.fasterxml.jackson.databind.ObjectMapper; |
| import com.google.common.testing.EqualsTester; |
| import java.io.IOException; |
| import java.io.NotSerializableException; |
| import java.io.Serializable; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| import org.apache.beam.sdk.testing.NeedsRunner; |
| import org.apache.beam.sdk.testing.TestPipeline; |
| import org.apache.beam.sdk.transforms.Create; |
| import org.apache.beam.sdk.transforms.display.DisplayData; |
| import org.apache.beam.sdk.util.SerializableUtils; |
| import org.apache.beam.sdk.util.common.ReflectHelpers; |
| import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.ImmutableList; |
| import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.ImmutableMap; |
| import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.ImmutableSet; |
| import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.Maps; |
| import org.hamcrest.Matchers; |
| import org.joda.time.Instant; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.experimental.categories.Category; |
| import org.junit.rules.ExpectedException; |
| import org.junit.rules.ExternalResource; |
| import org.junit.rules.TestRule; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** Tests for {@link ProxyInvocationHandler}. */ |
| @RunWith(JUnit4.class) |
| public class ProxyInvocationHandlerTest { |
| @Rule public ExpectedException expectedException = ExpectedException.none(); |
| |
| @Rule |
| public TestRule resetPipelineOptionsRegistry = |
| new ExternalResource() { |
| @Override |
| protected void before() { |
| PipelineOptionsFactory.resetCache(); |
| } |
| }; |
| |
| private static final ObjectMapper MAPPER = |
| new ObjectMapper() |
| .registerModules(ObjectMapper.findModules(ReflectHelpers.findClassLoader())); |
| |
| /** A test interface with some primitives and objects. */ |
| public interface Simple extends PipelineOptions { |
| boolean isOptionEnabled(); |
| |
| void setOptionEnabled(boolean value); |
| |
| int getPrimitive(); |
| |
| void setPrimitive(int value); |
| |
| String getString(); |
| |
| void setString(String value); |
| } |
| |
| @Rule public TestPipeline p = TestPipeline.create(); |
| |
| @Test |
| public void testPropertySettingAndGetting() throws Exception { |
| ProxyInvocationHandler handler = new ProxyInvocationHandler(Maps.newHashMap()); |
| Simple proxy = handler.as(Simple.class); |
| proxy.setString("OBJECT"); |
| proxy.setOptionEnabled(true); |
| proxy.setPrimitive(4); |
| assertEquals("OBJECT", proxy.getString()); |
| assertTrue(proxy.isOptionEnabled()); |
| assertEquals(4, proxy.getPrimitive()); |
| } |
| |
| /** A test interface containing all the JLS default values. */ |
| public interface JLSDefaults extends PipelineOptions { |
| boolean getBoolean(); |
| |
| void setBoolean(boolean value); |
| |
| char getChar(); |
| |
| void setChar(char value); |
| |
| byte getByte(); |
| |
| void setByte(byte value); |
| |
| short getShort(); |
| |
| void setShort(short value); |
| |
| int getInt(); |
| |
| void setInt(int value); |
| |
| long getLong(); |
| |
| void setLong(long value); |
| |
| float getFloat(); |
| |
| void setFloat(float value); |
| |
| double getDouble(); |
| |
| void setDouble(double value); |
| |
| Object getObject(); |
| |
| void setObject(Object value); |
| } |
| |
| @Test |
| public void testGettingJLSDefaults() throws Exception { |
| ProxyInvocationHandler handler = new ProxyInvocationHandler(Maps.newHashMap()); |
| JLSDefaults proxy = handler.as(JLSDefaults.class); |
| assertFalse(proxy.getBoolean()); |
| assertEquals('\0', proxy.getChar()); |
| assertEquals((byte) 0, proxy.getByte()); |
| assertEquals((short) 0, proxy.getShort()); |
| assertEquals(0, proxy.getInt()); |
| assertEquals(0L, proxy.getLong()); |
| assertEquals(0f, proxy.getFloat(), 0f); |
| assertEquals(0d, proxy.getDouble(), 0d); |
| assertNull(proxy.getObject()); |
| } |
| |
| /** A {@link DefaultValueFactory} that is used for testing. */ |
| public static class TestOptionFactory implements DefaultValueFactory<String> { |
| @Override |
| public String create(PipelineOptions options) { |
| return "testOptionFactory"; |
| } |
| } |
| |
| /** A test enum for testing {@link Default.Enum @Default.Enum}. */ |
| public enum EnumType { |
| MyEnum("MyTestEnum"); |
| |
| private final String value; |
| |
| EnumType(String value) { |
| this.value = value; |
| } |
| |
| @Override |
| public String toString() { |
| return value; |
| } |
| } |
| |
| /** A test interface containing all the {@link Default} annotations. */ |
| public interface DefaultAnnotations extends PipelineOptions { |
| @Default.Boolean(true) |
| boolean getBoolean(); |
| |
| void setBoolean(boolean value); |
| |
| @Default.Character('a') |
| char getChar(); |
| |
| void setChar(char value); |
| |
| @Default.Byte((byte) 4) |
| byte getByte(); |
| |
| void setByte(byte value); |
| |
| @Default.Short((short) 5) |
| short getShort(); |
| |
| void setShort(short value); |
| |
| @Default.Integer(6) |
| int getInt(); |
| |
| void setInt(int value); |
| |
| @Default.Long(7L) |
| long getLong(); |
| |
| void setLong(long value); |
| |
| @Default.Float(8f) |
| float getFloat(); |
| |
| void setFloat(float value); |
| |
| @Default.Double(9d) |
| double getDouble(); |
| |
| void setDouble(double value); |
| |
| @Default.String("testString") |
| String getString(); |
| |
| void setString(String value); |
| |
| @Default.Class(DefaultAnnotations.class) |
| Class<?> getClassOption(); |
| |
| void setClassOption(Class<?> value); |
| |
| @Default.Enum("MyEnum") |
| EnumType getEnum(); |
| |
| void setEnum(EnumType value); |
| |
| @Default.InstanceFactory(TestOptionFactory.class) |
| String getComplex(); |
| |
| void setComplex(String value); |
| } |
| |
| @Test |
| public void testAnnotationDefaults() throws Exception { |
| ProxyInvocationHandler handler = new ProxyInvocationHandler(Maps.newHashMap()); |
| DefaultAnnotations proxy = handler.as(DefaultAnnotations.class); |
| assertTrue(proxy.getBoolean()); |
| assertEquals('a', proxy.getChar()); |
| assertEquals((byte) 4, proxy.getByte()); |
| assertEquals((short) 5, proxy.getShort()); |
| assertEquals(6, proxy.getInt()); |
| assertEquals(7, proxy.getLong()); |
| assertEquals(8f, proxy.getFloat(), 0f); |
| assertEquals(9d, proxy.getDouble(), 0d); |
| assertEquals("testString", proxy.getString()); |
| assertEquals(DefaultAnnotations.class, proxy.getClassOption()); |
| assertEquals(EnumType.MyEnum, proxy.getEnum()); |
| assertEquals("testOptionFactory", proxy.getComplex()); |
| } |
| |
| @Test |
| public void testEqualsAndHashCode() throws Exception { |
| ProxyInvocationHandler handler = new ProxyInvocationHandler(Maps.newHashMap()); |
| Simple proxy = handler.as(Simple.class); |
| JLSDefaults sameAsProxy = proxy.as(JLSDefaults.class); |
| ProxyInvocationHandler handler2 = new ProxyInvocationHandler(Maps.newHashMap()); |
| Simple proxy2 = handler2.as(Simple.class); |
| JLSDefaults sameAsProxy2 = proxy2.as(JLSDefaults.class); |
| |
| new EqualsTester() |
| .addEqualityGroup(handler, proxy, sameAsProxy) |
| .addEqualityGroup(handler2, proxy2, sameAsProxy2) |
| .testEquals(); |
| } |
| |
| /** A test interface for string with default. */ |
| public interface StringWithDefault extends PipelineOptions { |
| @Default.String("testString") |
| String getString(); |
| |
| void setString(String value); |
| } |
| |
| @Test |
| public void testToString() throws Exception { |
| ProxyInvocationHandler handler = new ProxyInvocationHandler(Maps.newHashMap()); |
| StringWithDefault proxy = handler.as(StringWithDefault.class); |
| proxy.setString("stringValue"); |
| DefaultAnnotations proxy2 = proxy.as(DefaultAnnotations.class); |
| proxy2.setLong(57L); |
| assertEquals( |
| String.format("Current Settings:%n" + " long: 57%n" + " string: stringValue%n"), |
| proxy.toString()); |
| } |
| |
| @Test |
| public void testToStringAfterDeserializationContainsJsonEntries() throws Exception { |
| ProxyInvocationHandler handler = new ProxyInvocationHandler(Maps.newHashMap()); |
| StringWithDefault proxy = handler.as(StringWithDefault.class); |
| Long optionsId = proxy.getOptionsId(); |
| proxy.setString("stringValue"); |
| DefaultAnnotations proxy2 = proxy.as(DefaultAnnotations.class); |
| proxy2.setLong(57L); |
| assertEquals( |
| String.format( |
| "Current Settings:%n" |
| + " long: 57%n" |
| + " optionsId: %d%n" |
| + " string: \"stringValue\"%n", |
| optionsId), |
| serializeDeserialize(PipelineOptions.class, proxy2).toString()); |
| } |
| |
| @Test |
| public void testToStringAfterDeserializationContainsOverriddenEntries() throws Exception { |
| ProxyInvocationHandler handler = new ProxyInvocationHandler(Maps.newHashMap()); |
| StringWithDefault proxy = handler.as(StringWithDefault.class); |
| Long optionsId = proxy.getOptionsId(); |
| proxy.setString("stringValue"); |
| DefaultAnnotations proxy2 = proxy.as(DefaultAnnotations.class); |
| proxy2.setLong(57L); |
| Simple deserializedOptions = serializeDeserialize(Simple.class, proxy2); |
| deserializedOptions.setString("overriddenValue"); |
| assertEquals( |
| String.format( |
| "Current Settings:%n" |
| + " long: 57%n" |
| + " optionsId: %d%n" |
| + " string: overriddenValue%n", |
| optionsId), |
| deserializedOptions.toString()); |
| } |
| |
| /** A test interface containing an unknown method. */ |
| public interface UnknownMethod { |
| void unknownMethod(); |
| } |
| |
| @Test |
| public void testInvokeWithUnknownMethod() throws Exception { |
| expectedException.expect(RuntimeException.class); |
| expectedException.expectMessage( |
| "Unknown method [public abstract void " |
| + "org.apache.beam.sdk.options.ProxyInvocationHandlerTest$UnknownMethod.unknownMethod()] " |
| + "invoked with args [null]."); |
| |
| ProxyInvocationHandler handler = new ProxyInvocationHandler(Maps.newHashMap()); |
| handler.invoke(handler, UnknownMethod.class.getMethod("unknownMethod"), null); |
| } |
| |
| /** A test interface that extends another interface. */ |
| public interface SubClass extends Simple { |
| String getExtended(); |
| |
| void setExtended(String value); |
| } |
| |
| @Test |
| public void testSubClassStoresSuperInterfaceValues() throws Exception { |
| ProxyInvocationHandler handler = new ProxyInvocationHandler(Maps.newHashMap()); |
| SubClass extended = handler.as(SubClass.class); |
| |
| extended.setString("parentValue"); |
| assertEquals("parentValue", extended.getString()); |
| } |
| |
| @Test |
| public void testUpCastRetainsSuperInterfaceValues() throws Exception { |
| ProxyInvocationHandler handler = new ProxyInvocationHandler(Maps.newHashMap()); |
| SubClass extended = handler.as(SubClass.class); |
| |
| extended.setString("parentValue"); |
| Simple simple = extended.as(Simple.class); |
| assertEquals("parentValue", simple.getString()); |
| } |
| |
| @Test |
| public void testUpCastRetainsSubClassValues() throws Exception { |
| ProxyInvocationHandler handler = new ProxyInvocationHandler(Maps.newHashMap()); |
| SubClass extended = handler.as(SubClass.class); |
| |
| extended.setExtended("subClassValue"); |
| SubClass extended2 = extended.as(Simple.class).as(SubClass.class); |
| assertEquals("subClassValue", extended2.getExtended()); |
| } |
| |
| /** A test interface that is a sibling to {@link SubClass}. */ |
| public interface Sibling extends Simple { |
| String getSibling(); |
| |
| void setSibling(String value); |
| } |
| |
| @Test |
| public void testAsSiblingRetainsSuperInterfaceValues() throws Exception { |
| ProxyInvocationHandler handler = new ProxyInvocationHandler(Maps.newHashMap()); |
| SubClass extended = handler.as(SubClass.class); |
| |
| extended.setString("parentValue"); |
| Sibling sibling = extended.as(Sibling.class); |
| assertEquals("parentValue", sibling.getString()); |
| } |
| |
| /** A test interface that has the same methods as the parent. */ |
| public interface MethodConflict extends Simple { |
| @Override |
| String getString(); |
| |
| @Override |
| void setString(String value); |
| } |
| |
| @Test |
| public void testMethodConflictProvidesSameValue() throws Exception { |
| ProxyInvocationHandler handler = new ProxyInvocationHandler(Maps.newHashMap()); |
| MethodConflict methodConflict = handler.as(MethodConflict.class); |
| |
| methodConflict.setString("conflictValue"); |
| assertEquals("conflictValue", methodConflict.getString()); |
| assertEquals("conflictValue", methodConflict.as(Simple.class).getString()); |
| } |
| |
| /** A test interface that has the same methods as its parent and grandparent. */ |
| public interface DeepMethodConflict extends MethodConflict { |
| @Override |
| String getString(); |
| |
| @Override |
| void setString(String value); |
| |
| @Override |
| int getPrimitive(); |
| |
| @Override |
| void setPrimitive(int value); |
| } |
| |
| @Test |
| public void testDeepMethodConflictProvidesSameValue() throws Exception { |
| ProxyInvocationHandler handler = new ProxyInvocationHandler(Maps.newHashMap()); |
| DeepMethodConflict deepMethodConflict = handler.as(DeepMethodConflict.class); |
| |
| // Tests overriding an already overridden method |
| deepMethodConflict.setString("conflictValue"); |
| assertEquals("conflictValue", deepMethodConflict.getString()); |
| assertEquals("conflictValue", deepMethodConflict.as(MethodConflict.class).getString()); |
| assertEquals("conflictValue", deepMethodConflict.as(Simple.class).getString()); |
| |
| // Tests overriding a method from an ancestor class |
| deepMethodConflict.setPrimitive(5); |
| assertEquals(5, deepMethodConflict.getPrimitive()); |
| assertEquals(5, deepMethodConflict.as(MethodConflict.class).getPrimitive()); |
| assertEquals(5, deepMethodConflict.as(Simple.class).getPrimitive()); |
| } |
| |
| /** A test interface that shares the same methods as {@link Sibling}. */ |
| public interface SimpleSibling extends PipelineOptions { |
| String getString(); |
| |
| void setString(String value); |
| } |
| |
| @Test |
| public void testDisjointSiblingsShareValues() throws Exception { |
| ProxyInvocationHandler handler = new ProxyInvocationHandler(Maps.newHashMap()); |
| SimpleSibling proxy = handler.as(SimpleSibling.class); |
| proxy.setString("siblingValue"); |
| assertEquals("siblingValue", proxy.getString()); |
| assertEquals("siblingValue", proxy.as(Simple.class).getString()); |
| } |
| |
| /** A test interface that joins two sibling interfaces that have conflicting methods. */ |
| public interface SiblingMethodConflict extends Simple, SimpleSibling {} |
| |
| @Test |
| public void testSiblingMethodConflict() throws Exception { |
| ProxyInvocationHandler handler = new ProxyInvocationHandler(Maps.newHashMap()); |
| SiblingMethodConflict siblingMethodConflict = handler.as(SiblingMethodConflict.class); |
| siblingMethodConflict.setString("siblingValue"); |
| assertEquals("siblingValue", siblingMethodConflict.getString()); |
| assertEquals("siblingValue", siblingMethodConflict.as(Simple.class).getString()); |
| assertEquals("siblingValue", siblingMethodConflict.as(SimpleSibling.class).getString()); |
| } |
| |
| /** A test interface that has only the getter and only a setter overriden. */ |
| public interface PartialMethodConflict extends Simple { |
| @Override |
| String getString(); |
| |
| @Override |
| void setPrimitive(int value); |
| } |
| |
| @Test |
| public void testPartialMethodConflictProvidesSameValue() throws Exception { |
| ProxyInvocationHandler handler = new ProxyInvocationHandler(Maps.newHashMap()); |
| PartialMethodConflict partialMethodConflict = handler.as(PartialMethodConflict.class); |
| |
| // Tests overriding a getter property that is only partially bound |
| partialMethodConflict.setString("conflictValue"); |
| assertEquals("conflictValue", partialMethodConflict.getString()); |
| assertEquals("conflictValue", partialMethodConflict.as(Simple.class).getString()); |
| |
| // Tests overriding a setter property that is only partially bound |
| partialMethodConflict.setPrimitive(5); |
| assertEquals(5, partialMethodConflict.getPrimitive()); |
| assertEquals(5, partialMethodConflict.as(Simple.class).getPrimitive()); |
| } |
| |
| @Test |
| public void testResetRegistry() { |
| Set<Class<? extends PipelineOptions>> defaultRegistry = |
| new HashSet<>(PipelineOptionsFactory.getRegisteredOptions()); |
| assertThat(defaultRegistry, not(hasItem(FooOptions.class))); |
| |
| PipelineOptionsFactory.register(FooOptions.class); |
| assertThat(PipelineOptionsFactory.getRegisteredOptions(), hasItem(FooOptions.class)); |
| |
| PipelineOptionsFactory.resetCache(); |
| assertEquals(defaultRegistry, PipelineOptionsFactory.getRegisteredOptions()); |
| } |
| |
| @Test |
| public void testJsonConversionForDefault() throws Exception { |
| PipelineOptions options = PipelineOptionsFactory.create(); |
| assertNotNull(serializeDeserialize(PipelineOptions.class, options)); |
| } |
| |
| /** Test interface for JSON conversion of simple types. */ |
| public interface SimpleTypes extends PipelineOptions { |
| int getInteger(); |
| |
| void setInteger(int value); |
| |
| String getString(); |
| |
| void setString(String value); |
| } |
| |
| @Test |
| public void testJsonConversionForSimpleTypes() throws Exception { |
| SimpleTypes options = PipelineOptionsFactory.as(SimpleTypes.class); |
| options.setString("TestValue"); |
| options.setInteger(5); |
| SimpleTypes options2 = serializeDeserialize(SimpleTypes.class, options); |
| assertEquals(5, options2.getInteger()); |
| assertEquals("TestValue", options2.getString()); |
| } |
| |
| @Test |
| public void testJsonConversionOfAJsonConvertedType() throws Exception { |
| SimpleTypes options = PipelineOptionsFactory.as(SimpleTypes.class); |
| options.setString("TestValue"); |
| options.setInteger(5); |
| // It is important here that our first serialization goes to our most basic |
| // type so that we handle the case when we don't know the types of certain |
| // properties because the intermediate instance of PipelineOptions never |
| // saw their interface. |
| SimpleTypes options2 = |
| serializeDeserialize( |
| SimpleTypes.class, serializeDeserialize(PipelineOptions.class, options)); |
| assertEquals(5, options2.getInteger()); |
| assertEquals("TestValue", options2.getString()); |
| } |
| |
| @Test |
| public void testJsonConversionForPartiallySerializedValues() throws Exception { |
| SimpleTypes options = PipelineOptionsFactory.as(SimpleTypes.class); |
| options.setInteger(5); |
| SimpleTypes options2 = serializeDeserialize(SimpleTypes.class, options); |
| options2.setString("TestValue"); |
| SimpleTypes options3 = serializeDeserialize(SimpleTypes.class, options2); |
| assertEquals(5, options3.getInteger()); |
| assertEquals("TestValue", options3.getString()); |
| } |
| |
| @Test |
| public void testJsonConversionForOverriddenSerializedValues() throws Exception { |
| SimpleTypes options = PipelineOptionsFactory.as(SimpleTypes.class); |
| options.setInteger(-5); |
| options.setString("NeedsToBeOverridden"); |
| SimpleTypes options2 = serializeDeserialize(SimpleTypes.class, options); |
| options2.setInteger(5); |
| options2.setString("TestValue"); |
| SimpleTypes options3 = serializeDeserialize(SimpleTypes.class, options2); |
| assertEquals(5, options3.getInteger()); |
| assertEquals("TestValue", options3.getString()); |
| } |
| |
| /** Test interface for JSON conversion of container types. */ |
| public interface ContainerTypes extends PipelineOptions { |
| List<String> getList(); |
| |
| void setList(List<String> values); |
| |
| Map<String, String> getMap(); |
| |
| void setMap(Map<String, String> values); |
| |
| Set<String> getSet(); |
| |
| void setSet(Set<String> values); |
| } |
| |
| @Test |
| public void testJsonConversionForContainerTypes() throws Exception { |
| List<String> list = ImmutableList.of("a", "b", "c"); |
| Map<String, String> map = ImmutableMap.of("d", "x", "e", "y", "f", "z"); |
| Set<String> set = ImmutableSet.of("g", "h", "i"); |
| ContainerTypes options = PipelineOptionsFactory.as(ContainerTypes.class); |
| options.setList(list); |
| options.setMap(map); |
| options.setSet(set); |
| ContainerTypes options2 = serializeDeserialize(ContainerTypes.class, options); |
| assertEquals(list, options2.getList()); |
| assertEquals(map, options2.getMap()); |
| assertEquals(set, options2.getSet()); |
| } |
| |
| /** Test interface for conversion of inner types. */ |
| public static class InnerType { |
| public double doubleField; |
| |
| static InnerType of(double value) { |
| InnerType rval = new InnerType(); |
| rval.doubleField = value; |
| return rval; |
| } |
| |
| @Override |
| public int hashCode() { |
| return 0; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| return obj != null |
| && getClass().equals(obj.getClass()) |
| && Objects.equals(doubleField, ((InnerType) obj).doubleField); |
| } |
| } |
| |
| /** Test interface for conversion of generics and inner types. */ |
| public static class ComplexType { |
| public String stringField; |
| public Integer intField; |
| public List<InnerType> genericType; |
| public InnerType innerType; |
| |
| @Override |
| public int hashCode() { |
| return 0; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| return obj != null |
| && getClass().equals(obj.getClass()) |
| && Objects.equals(stringField, ((ComplexType) obj).stringField) |
| && Objects.equals(intField, ((ComplexType) obj).intField) |
| && Objects.equals(genericType, ((ComplexType) obj).genericType) |
| && Objects.equals(innerType, ((ComplexType) obj).innerType); |
| } |
| } |
| |
| /** Test interface. */ |
| public interface ComplexTypes extends PipelineOptions { |
| ComplexType getComplexType(); |
| |
| void setComplexType(ComplexType value); |
| } |
| |
| @Test |
| public void testJsonConversionForComplexType() throws Exception { |
| ComplexType complexType = new ComplexType(); |
| complexType.stringField = "stringField"; |
| complexType.intField = 12; |
| complexType.innerType = InnerType.of(12); |
| complexType.genericType = ImmutableList.of(InnerType.of(16234), InnerType.of(24)); |
| |
| ComplexTypes options = PipelineOptionsFactory.as(ComplexTypes.class); |
| options.setComplexType(complexType); |
| ComplexTypes options2 = serializeDeserialize(ComplexTypes.class, options); |
| assertEquals(complexType, options2.getComplexType()); |
| } |
| |
| /** Test interface for testing ignored properties during serialization. */ |
| public interface IgnoredProperty extends PipelineOptions { |
| @JsonIgnore |
| String getValue(); |
| |
| void setValue(String value); |
| } |
| |
| @Test |
| public void testJsonConversionOfIgnoredProperty() throws Exception { |
| IgnoredProperty options = PipelineOptionsFactory.as(IgnoredProperty.class); |
| options.setValue("TestValue"); |
| |
| IgnoredProperty options2 = serializeDeserialize(IgnoredProperty.class, options); |
| assertNull(options2.getValue()); |
| } |
| |
| /** Test class that is not serializable by Jackson. */ |
| public static class NotSerializable { |
| private String value; |
| |
| public NotSerializable(String value) { |
| this.value = value; |
| } |
| |
| public String getValue() { |
| return value; |
| } |
| } |
| |
| /** Test interface containing a class that is not serializable by Jackson. */ |
| public interface NotSerializableProperty extends PipelineOptions { |
| NotSerializable getValue(); |
| |
| void setValue(NotSerializable value); |
| } |
| |
| @Test |
| public void testJsonConversionOfNotSerializableProperty() throws Exception { |
| NotSerializableProperty options = PipelineOptionsFactory.as(NotSerializableProperty.class); |
| options.setValue(new NotSerializable("TestString")); |
| |
| expectedException.expect(JsonMappingException.class); |
| expectedException.expectMessage("Failed to serialize and deserialize property 'value'"); |
| serializeDeserialize(NotSerializableProperty.class, options); |
| } |
| |
| /** |
| * Test interface that has {@link JsonIgnore @JsonIgnore} on a property that Jackson can't |
| * serialize. |
| */ |
| public interface IgnoredNotSerializableProperty extends PipelineOptions { |
| @JsonIgnore |
| NotSerializable getValue(); |
| |
| void setValue(NotSerializable value); |
| } |
| |
| @Test |
| public void testJsonConversionOfIgnoredNotSerializableProperty() throws Exception { |
| IgnoredNotSerializableProperty options = |
| PipelineOptionsFactory.as(IgnoredNotSerializableProperty.class); |
| options.setValue(new NotSerializable("TestString")); |
| |
| IgnoredNotSerializableProperty options2 = |
| serializeDeserialize(IgnoredNotSerializableProperty.class, options); |
| assertNull(options2.getValue()); |
| } |
| |
| /** Test class that is only serializable by Jackson with the added metadata. */ |
| public static class SerializableWithMetadata { |
| private String value; |
| |
| public SerializableWithMetadata(@JsonProperty("value") String value) { |
| this.value = value; |
| } |
| |
| @JsonProperty("value") |
| public String getValue() { |
| return value; |
| } |
| } |
| |
| /** |
| * Test interface containing a property that is serializable by Jackson only with the additional |
| * metadata. |
| */ |
| public interface SerializableWithMetadataProperty extends PipelineOptions { |
| SerializableWithMetadata getValue(); |
| |
| void setValue(SerializableWithMetadata value); |
| } |
| |
| @Test |
| public void testJsonConversionOfSerializableWithMetadataProperty() throws Exception { |
| SerializableWithMetadataProperty options = |
| PipelineOptionsFactory.as(SerializableWithMetadataProperty.class); |
| options.setValue(new SerializableWithMetadata("TestString")); |
| |
| SerializableWithMetadataProperty options2 = |
| serializeDeserialize(SerializableWithMetadataProperty.class, options); |
| assertEquals("TestString", options2.getValue().getValue()); |
| } |
| |
| @Test |
| public void testDisplayDataItemProperties() { |
| PipelineOptions options = PipelineOptionsFactory.create(); |
| options.setTempLocation("myTemp"); |
| DisplayData displayData = DisplayData.from(options); |
| |
| assertThat( |
| displayData, |
| hasDisplayItem( |
| allOf( |
| hasKey("tempLocation"), |
| hasType(DisplayData.Type.STRING), |
| hasValue("myTemp"), |
| hasNamespace(PipelineOptions.class)))); |
| } |
| |
| @Test |
| public void testDisplayDataTypes() { |
| Instant now = Instant.now(); |
| |
| TypedOptions options = PipelineOptionsFactory.as(TypedOptions.class); |
| options.setInteger(1234); |
| options.setTimestamp(now); |
| options.setJavaClass(ProxyInvocationHandlerTest.class); |
| options.setObject( |
| new Serializable() { |
| @Override |
| public String toString() { |
| return "foobar"; |
| } |
| }); |
| |
| DisplayData displayData = DisplayData.from(options); |
| |
| assertThat(displayData, hasDisplayItem("integer", 1234)); |
| assertThat(displayData, hasDisplayItem("timestamp", now)); |
| assertThat(displayData, hasDisplayItem("javaClass", ProxyInvocationHandlerTest.class)); |
| assertThat(displayData, hasDisplayItem("object", "foobar")); |
| } |
| |
| /** Test interface. */ |
| public interface TypedOptions extends PipelineOptions { |
| int getInteger(); |
| |
| void setInteger(int value); |
| |
| Instant getTimestamp(); |
| |
| void setTimestamp(Instant value); |
| |
| Class<?> getJavaClass(); |
| |
| void setJavaClass(Class<?> value); |
| |
| Object getObject(); |
| |
| void setObject(Object value); |
| } |
| |
| @Test |
| @Category(NeedsRunner.class) |
| public void pipelineOptionsDisplayDataExceptionShouldFail() { |
| Object brokenValueType = |
| new Object() { |
| @JsonValue |
| public int getValue() { |
| return 42; |
| } |
| |
| @Override |
| public String toString() { |
| throw new RuntimeException("oh noes!!"); |
| } |
| }; |
| |
| p.getOptions().as(ObjectPipelineOptions.class).setValue(brokenValueType); |
| |
| p.apply(Create.of(1, 2, 3)); |
| |
| expectedException.expectMessage( |
| ProxyInvocationHandler.PipelineOptionsDisplayData.class.getName()); |
| expectedException.expectMessage("oh noes!!"); |
| p.run(); |
| } |
| |
| /** {@link PipelineOptions} to inject bad object implementations. */ |
| public interface ObjectPipelineOptions extends PipelineOptions { |
| Object getValue(); |
| |
| void setValue(Object value); |
| } |
| |
| @Test |
| public void testDisplayDataInheritanceNamespace() { |
| ExtendsBaseOptions options = PipelineOptionsFactory.as(ExtendsBaseOptions.class); |
| options.setFoo("bar"); |
| |
| DisplayData displayData = DisplayData.from(options); |
| |
| assertThat( |
| displayData, |
| hasDisplayItem( |
| allOf(hasKey("foo"), hasValue("bar"), hasNamespace(ExtendsBaseOptions.class)))); |
| } |
| |
| /** Test interface. */ |
| public interface BaseOptions extends PipelineOptions { |
| String getFoo(); |
| |
| void setFoo(String value); |
| } |
| |
| /** Test interface. */ |
| public interface ExtendsBaseOptions extends BaseOptions { |
| @Override |
| String getFoo(); |
| |
| @Override |
| void setFoo(String value); |
| } |
| |
| @Test |
| public void testDisplayDataExcludedFromOverriddenBaseClass() { |
| ExtendsBaseOptions options = PipelineOptionsFactory.as(ExtendsBaseOptions.class); |
| options.setFoo("bar"); |
| |
| DisplayData displayData = DisplayData.from(options); |
| assertThat(displayData, not(hasDisplayItem(hasNamespace(BaseOptions.class)))); |
| } |
| |
| @Test |
| public void testDisplayDataIncludedForDisjointInterfaceHierarchies() { |
| FooOptions fooOptions = PipelineOptionsFactory.as(FooOptions.class); |
| fooOptions.setFoo("foo"); |
| |
| BarOptions barOptions = fooOptions.as(BarOptions.class); |
| barOptions.setBar("bar"); |
| |
| DisplayData data = DisplayData.from(barOptions); |
| assertThat(data, hasDisplayItem(allOf(hasKey("foo"), hasNamespace(FooOptions.class)))); |
| assertThat(data, hasDisplayItem(allOf(hasKey("bar"), hasNamespace(BarOptions.class)))); |
| } |
| |
| /** Test interface. */ |
| public interface FooOptions extends PipelineOptions { |
| String getFoo(); |
| |
| void setFoo(String value); |
| } |
| |
| /** Test interface. */ |
| public interface BarOptions extends PipelineOptions { |
| String getBar(); |
| |
| void setBar(String value); |
| } |
| |
| @Test |
| public void testDisplayDataExcludesDefaultValues() { |
| PipelineOptions options = PipelineOptionsFactory.as(HasDefaults.class); |
| DisplayData data = DisplayData.from(options); |
| |
| assertThat(data, not(hasDisplayItem("foo"))); |
| } |
| |
| /** Test interface. */ |
| public interface HasDefaults extends PipelineOptions { |
| @Default.String("bar") |
| String getFoo(); |
| |
| void setFoo(String value); |
| } |
| |
| @Test |
| public void testDisplayDataExcludesValuesAccessedButNeverSet() { |
| HasDefaults options = PipelineOptionsFactory.as(HasDefaults.class); |
| assertEquals("bar", options.getFoo()); |
| |
| DisplayData data = DisplayData.from(options); |
| assertThat(data, not(hasDisplayItem("foo"))); |
| } |
| |
| @Test |
| public void testDisplayDataIncludesExplicitlySetDefaults() { |
| HasDefaults options = PipelineOptionsFactory.as(HasDefaults.class); |
| String defaultValue = options.getFoo(); |
| options.setFoo(defaultValue); |
| |
| DisplayData data = DisplayData.from(options); |
| assertThat(data, hasDisplayItem("foo", defaultValue)); |
| } |
| |
| @Test |
| public void testDisplayDataNullValuesConvertedToEmptyString() throws Exception { |
| FooOptions options = PipelineOptionsFactory.as(FooOptions.class); |
| options.setFoo(null); |
| |
| DisplayData data = DisplayData.from(options); |
| assertThat(data, hasDisplayItem("foo", "")); |
| |
| FooOptions deserializedOptions = serializeDeserialize(FooOptions.class, options); |
| DisplayData deserializedData = DisplayData.from(deserializedOptions); |
| assertThat(deserializedData, hasDisplayItem("foo", "")); |
| } |
| |
| @Test |
| public void testDisplayDataArrayValue() throws Exception { |
| ArrayOptions options = PipelineOptionsFactory.as(ArrayOptions.class); |
| options.setDeepArray(new String[][] {new String[] {"a", "b"}, new String[] {"c"}}); |
| options.setDeepPrimitiveArray(new int[][] {new int[] {1, 2}, new int[] {3}}); |
| |
| DisplayData data = DisplayData.from(options); |
| assertThat(data, hasDisplayItem("deepArray", "[[a, b], [c]]")); |
| assertThat(data, hasDisplayItem("deepPrimitiveArray", "[[1, 2], [3]]")); |
| |
| ArrayOptions deserializedOptions = serializeDeserialize(ArrayOptions.class, options); |
| DisplayData deserializedData = DisplayData.from(deserializedOptions); |
| assertThat(deserializedData, hasDisplayItem("deepPrimitiveArray", "[[1, 2], [3]]")); |
| } |
| |
| /** Test interface. */ |
| public interface ArrayOptions extends PipelineOptions { |
| String[][] getDeepArray(); |
| |
| void setDeepArray(String[][] value); |
| |
| int[][] getDeepPrimitiveArray(); |
| |
| void setDeepPrimitiveArray(int[][] value); |
| } |
| |
| @Test |
| public void testDisplayDataJsonSerialization() throws IOException { |
| FooOptions options = PipelineOptionsFactory.as(FooOptions.class); |
| options.setFoo("bar"); |
| |
| @SuppressWarnings("unchecked") |
| Map<String, Object> map = MAPPER.readValue(MAPPER.writeValueAsBytes(options), Map.class); |
| |
| assertThat("main pipeline options data keyed as 'options'", map, Matchers.hasKey("options")); |
| assertThat("display data keyed as 'display_data'", map, Matchers.hasKey("display_data")); |
| |
| Map<?, ?> expectedDisplayItem = |
| ImmutableMap.<String, String>builder() |
| .put("namespace", FooOptions.class.getName()) |
| .put("key", "foo") |
| .put("value", "bar") |
| .put("type", "STRING") |
| .build(); |
| |
| @SuppressWarnings("unchecked") |
| List<Map<?, ?>> deserializedDisplayData = (List<Map<?, ?>>) map.get("display_data"); |
| assertThat(deserializedDisplayData, hasItem(expectedDisplayItem)); |
| } |
| |
| @Test |
| public void testDisplayDataFromDeserializedJson() throws Exception { |
| FooOptions options = PipelineOptionsFactory.as(FooOptions.class); |
| options.setFoo("bar"); |
| DisplayData data = DisplayData.from(options); |
| assertThat(data, hasDisplayItem("foo", "bar")); |
| |
| FooOptions deserializedOptions = serializeDeserialize(FooOptions.class, options); |
| DisplayData dataAfterDeserialization = DisplayData.from(deserializedOptions); |
| assertEquals(data, dataAfterDeserialization); |
| } |
| |
| @Test |
| public void testDisplayDataDeserializationWithRegistration() throws Exception { |
| PipelineOptionsFactory.register(HasClassOptions.class); |
| HasClassOptions options = PipelineOptionsFactory.as(HasClassOptions.class); |
| options.setClassOption(ProxyInvocationHandlerTest.class); |
| |
| PipelineOptions deserializedOptions = serializeDeserialize(PipelineOptions.class, options); |
| DisplayData displayData = DisplayData.from(deserializedOptions); |
| assertThat(displayData, hasDisplayItem("classOption", ProxyInvocationHandlerTest.class)); |
| } |
| |
| @Test |
| public void testDisplayDataMissingPipelineOptionsRegistration() throws Exception { |
| HasClassOptions options = PipelineOptionsFactory.as(HasClassOptions.class); |
| options.setClassOption(ProxyInvocationHandlerTest.class); |
| |
| PipelineOptions deserializedOptions = serializeDeserialize(PipelineOptions.class, options); |
| DisplayData displayData = DisplayData.from(deserializedOptions); |
| String expectedJsonValue = MAPPER.writeValueAsString(ProxyInvocationHandlerTest.class); |
| assertThat(displayData, hasDisplayItem("classOption", expectedJsonValue)); |
| } |
| |
| /** Test interface. */ |
| public interface HasClassOptions extends PipelineOptions { |
| Class<?> getClassOption(); |
| |
| void setClassOption(Class<?> value); |
| } |
| |
| @Test |
| public void testDisplayDataJsonValueSetAfterDeserialization() throws Exception { |
| FooOptions options = PipelineOptionsFactory.as(FooOptions.class); |
| options.setFoo("bar"); |
| DisplayData data = DisplayData.from(options); |
| assertThat(data, hasDisplayItem("foo", "bar")); |
| |
| FooOptions deserializedOptions = serializeDeserialize(FooOptions.class, options); |
| deserializedOptions.setFoo("baz"); |
| DisplayData dataAfterDeserialization = DisplayData.from(deserializedOptions); |
| assertThat(dataAfterDeserialization, hasDisplayItem("foo", "baz")); |
| } |
| |
| private <T extends PipelineOptions> T serializeDeserialize(Class<T> kls, PipelineOptions options) |
| throws Exception { |
| String value = MAPPER.writeValueAsString(options); |
| return MAPPER.readValue(value, PipelineOptions.class).as(kls); |
| } |
| |
| @Test |
| public void testDisplayDataExcludesJsonIgnoreOptions() { |
| IgnoredProperty options = PipelineOptionsFactory.as(IgnoredProperty.class); |
| options.setValue("foobar"); |
| |
| DisplayData data = DisplayData.from(options); |
| assertThat(data, not(hasDisplayItem("value"))); |
| } |
| |
| private static class CapturesOptions implements Serializable { |
| PipelineOptions options = PipelineOptionsFactory.create(); |
| } |
| |
| @Test |
| public void testOptionsAreNotSerializable() { |
| expectedException.expectCause(Matchers.instanceOf(NotSerializableException.class)); |
| SerializableUtils.clone(new CapturesOptions()); |
| } |
| |
| @Test |
| public void testGetOptionNameFromMethod() throws NoSuchMethodException { |
| ProxyInvocationHandler handler = new ProxyInvocationHandler(Maps.newHashMap()); |
| handler.as(BaseOptions.class); |
| assertEquals("foo", handler.getOptionName(BaseOptions.class.getMethod("getFoo"))); |
| } |
| } |