blob: 5f91baa24223d82aab7b093f1ca48b8b6ff8fe79 [file] [log] [blame]
/*
* 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 java.util.Locale.ROOT;
import static org.apache.beam.vendor.guava.v20_0.com.google.common.collect.Maps.uniqueIndex;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.isEmptyString;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
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.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.google.auto.service.AutoService;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.beam.model.jobmanagement.v1.JobApi.PipelineOptionDescriptor;
import org.apache.beam.model.jobmanagement.v1.JobApi.PipelineOptionType;
import org.apache.beam.sdk.Pipeline;
import org.apache.beam.sdk.PipelineResult;
import org.apache.beam.sdk.PipelineRunner;
import org.apache.beam.sdk.runners.PipelineRunnerRegistrar;
import org.apache.beam.sdk.testing.CrashingRunner;
import org.apache.beam.sdk.testing.ExpectedLogs;
import org.apache.beam.sdk.testing.InterceptingUrlClassLoader;
import org.apache.beam.sdk.testing.RestoreSystemProperties;
import org.apache.beam.sdk.util.common.ReflectHelpers;
import org.apache.beam.vendor.guava.v20_0.com.google.common.base.Charsets;
import org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ArrayListMultimap;
import org.apache.beam.vendor.guava.v20_0.com.google.common.collect.Collections2;
import org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ImmutableList;
import org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ImmutableMap;
import org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ListMultimap;
import org.apache.beam.vendor.guava.v20_0.com.google.common.collect.Sets;
import org.hamcrest.Matchers;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests for {@link PipelineOptionsFactory}. */
@RunWith(JUnit4.class)
public class PipelineOptionsFactoryTest {
private static final String DEFAULT_RUNNER_NAME = "DirectRunner";
private static final Class<? extends PipelineRunner<?>> REGISTERED_RUNNER =
RegisteredTestRunner.class;
@Rule public ExpectedException expectedException = ExpectedException.none();
@Rule public TestRule restoreSystemProperties = new RestoreSystemProperties();
@Rule public ExpectedLogs expectedLogs = ExpectedLogs.none(PipelineOptionsFactory.class);
@Test
public void testAutomaticRegistrationOfPipelineOptions() {
assertTrue(PipelineOptionsFactory.getRegisteredOptions().contains(RegisteredTestOptions.class));
}
@Test
public void testAutomaticRegistrationOfRunners() {
assertEquals(
REGISTERED_RUNNER,
PipelineOptionsFactory.getRegisteredRunners()
.get(REGISTERED_RUNNER.getSimpleName().toLowerCase()));
}
@Test
public void testAutomaticRegistrationInculdesWithoutRunnerSuffix() {
// Sanity check to make sure the substring works appropriately
assertEquals(
"RegisteredTest",
REGISTERED_RUNNER
.getSimpleName()
.substring(0, REGISTERED_RUNNER.getSimpleName().length() - "Runner".length()));
Map<String, Class<? extends PipelineRunner<?>>> registered =
PipelineOptionsFactory.CACHE.get().getSupportedPipelineRunners();
assertEquals(
REGISTERED_RUNNER,
registered.get(
REGISTERED_RUNNER
.getSimpleName()
.toLowerCase()
.substring(0, REGISTERED_RUNNER.getSimpleName().length() - "Runner".length())));
}
@Test
public void testAppNameIsSet() {
ApplicationNameOptions options = PipelineOptionsFactory.as(ApplicationNameOptions.class);
assertEquals(PipelineOptionsFactoryTest.class.getSimpleName(), options.getAppName());
}
/** A simple test interface. */
public interface TestPipelineOptions extends PipelineOptions {
String getTestPipelineOption();
void setTestPipelineOption(String value);
}
@Test
public void testAppNameIsSetWhenUsingAs() {
TestPipelineOptions options = PipelineOptionsFactory.as(TestPipelineOptions.class);
assertEquals(
PipelineOptionsFactoryTest.class.getSimpleName(),
options.as(ApplicationNameOptions.class).getAppName());
}
@Test
public void testOptionsIdIsSet() throws Exception {
ObjectMapper mapper =
new ObjectMapper()
.registerModules(ObjectMapper.findModules(ReflectHelpers.findClassLoader()));
PipelineOptions options = PipelineOptionsFactory.create();
// We purposely serialize/deserialize to get another instance. This allows to test if the
// default has been set or not.
PipelineOptions clone =
mapper.readValue(mapper.writeValueAsString(options), PipelineOptions.class);
// It is important that we don't call getOptionsId() before we have created the clone.
assertEquals(options.getOptionsId(), clone.getOptionsId());
}
@Test
public void testManualRegistration() {
assertFalse(PipelineOptionsFactory.getRegisteredOptions().contains(TestPipelineOptions.class));
PipelineOptionsFactory.register(TestPipelineOptions.class);
assertTrue(PipelineOptionsFactory.getRegisteredOptions().contains(TestPipelineOptions.class));
}
@Test
public void testDefaultRegistration() {
assertTrue(PipelineOptionsFactory.getRegisteredOptions().contains(PipelineOptions.class));
}
/** A test interface missing a getter. */
public interface MissingGetter extends PipelineOptions {
void setObject(Object value);
}
@Test
public void testMissingGetterThrows() throws Exception {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
"Expected getter for property [object] of type [java.lang.Object] on "
+ "[org.apache.beam.sdk.options.PipelineOptionsFactoryTest$MissingGetter].");
PipelineOptionsFactory.as(MissingGetter.class);
}
/** A test interface missing multiple getters. */
public interface MissingMultipleGetters extends MissingGetter {
void setOtherObject(Object value);
}
@Test
public void testMultipleMissingGettersThrows() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
"missing property methods on [org.apache.beam.sdk.options."
+ "PipelineOptionsFactoryTest$MissingMultipleGetters]");
expectedException.expectMessage("getter for property [object] of type [java.lang.Object]");
expectedException.expectMessage("getter for property [otherObject] of type [java.lang.Object]");
PipelineOptionsFactory.as(MissingMultipleGetters.class);
}
/** A test interface missing a setter. */
public interface MissingSetter extends PipelineOptions {
Object getObject();
}
@Test
public void testMissingSetterThrows() throws Exception {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
"Expected setter for property [object] of type [java.lang.Object] on "
+ "[org.apache.beam.sdk.options.PipelineOptionsFactoryTest$MissingSetter].");
PipelineOptionsFactory.as(MissingSetter.class);
}
/** A test interface missing multiple setters. */
public interface MissingMultipleSetters extends MissingSetter {
Object getOtherObject();
}
@Test
public void testMissingMultipleSettersThrows() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
"missing property methods on [org.apache.beam.sdk.options."
+ "PipelineOptionsFactoryTest$MissingMultipleSetters]");
expectedException.expectMessage("setter for property [object] of type [java.lang.Object]");
expectedException.expectMessage("setter for property [otherObject] of type [java.lang.Object]");
PipelineOptionsFactory.as(MissingMultipleSetters.class);
}
/** A test interface missing a setter and a getter. */
public interface MissingGettersAndSetters extends MissingGetter {
Object getOtherObject();
}
@Test
public void testMissingGettersAndSettersThrows() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
"missing property methods on [org.apache.beam.sdk.options."
+ "PipelineOptionsFactoryTest$MissingGettersAndSetters]");
expectedException.expectMessage("getter for property [object] of type [java.lang.Object]");
expectedException.expectMessage("setter for property [otherObject] of type [java.lang.Object]");
PipelineOptionsFactory.as(MissingGettersAndSetters.class);
}
/** A test interface with a type mismatch between the getter and setter. */
public interface GetterSetterTypeMismatch extends PipelineOptions {
boolean getValue();
void setValue(int value);
}
@Test
public void testGetterSetterTypeMismatchThrows() throws Exception {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
"Type mismatch between getter and setter methods for property [value]. Getter is of type "
+ "[boolean] whereas setter is of type [int].");
PipelineOptionsFactory.as(GetterSetterTypeMismatch.class);
}
/** A test interface with multiple type mismatches between getters and setters. */
public interface MultiGetterSetterTypeMismatch extends GetterSetterTypeMismatch {
long getOther();
void setOther(String other);
}
@Test
public void testMultiGetterSetterTypeMismatchThrows() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Type mismatches between getters and setters detected:");
expectedException.expectMessage(
"Property [value]: Getter is of type " + "[boolean] whereas setter is of type [int].");
expectedException.expectMessage(
"Property [other]: Getter is of type [long] "
+ "whereas setter is of type [class java.lang.String].");
PipelineOptionsFactory.as(MultiGetterSetterTypeMismatch.class);
}
/** A test interface representing a composite interface. */
public interface CombinedObject extends MissingGetter, MissingSetter {}
@Test
public void testHavingSettersGettersFromSeparateInterfacesIsValid() {
PipelineOptionsFactory.as(CombinedObject.class);
}
/** A test interface that contains a non-bean style method. */
public interface ExtraneousMethod extends PipelineOptions {
String extraneousMethod(int value, String otherValue);
}
@Test
public void testHavingExtraneousMethodThrows() throws Exception {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
"Methods [extraneousMethod(int, String)] on "
+ "[org.apache.beam.sdk.options.PipelineOptionsFactoryTest$ExtraneousMethod] "
+ "do not conform to being bean properties.");
PipelineOptionsFactory.as(ExtraneousMethod.class);
}
/** A test interface that has a conflicting return type with its parent. */
public interface ReturnTypeConflict extends CombinedObject {
@Override
String getObject();
void setObject(String value);
}
@Test
public void testReturnTypeConflictThrows() throws Exception {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
"Method [getObject] has multiple definitions [public abstract java.lang.Object "
+ "org.apache.beam.sdk.options.PipelineOptionsFactoryTest$MissingSetter"
+ ".getObject(), public abstract java.lang.String "
+ "org.apache.beam.sdk.options.PipelineOptionsFactoryTest$ReturnTypeConflict"
+ ".getObject()] with different return types for ["
+ "org.apache.beam.sdk.options.PipelineOptionsFactoryTest$ReturnTypeConflict].");
PipelineOptionsFactory.as(ReturnTypeConflict.class);
}
/** An interface to provide multiple methods with return type conflicts. */
public interface MultiReturnTypeConflictBase extends CombinedObject {
Object getOther();
void setOther(Object object);
}
/** A test interface that has multiple conflicting return types with its parent. */
public interface MultiReturnTypeConflict extends MultiReturnTypeConflictBase {
@Override
String getObject();
void setObject(String value);
@Override
Long getOther();
void setOther(Long other);
}
@Test
public void testMultipleReturnTypeConflictsThrows() throws Exception {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
"[org.apache.beam.sdk.options." + "PipelineOptionsFactoryTest$MultiReturnTypeConflict]");
expectedException.expectMessage(
"Methods with multiple definitions with different return types");
expectedException.expectMessage("Method [getObject] has multiple definitions");
expectedException.expectMessage(
"public abstract java.lang.Object "
+ "org.apache.beam.sdk.options.PipelineOptionsFactoryTest$"
+ "MissingSetter.getObject()");
expectedException.expectMessage(
"public abstract java.lang.String org.apache.beam.sdk.options."
+ "PipelineOptionsFactoryTest$MultiReturnTypeConflict.getObject()");
expectedException.expectMessage("Method [getOther] has multiple definitions");
expectedException.expectMessage(
"public abstract java.lang.Object "
+ "org.apache.beam.sdk.options.PipelineOptionsFactoryTest$"
+ "MultiReturnTypeConflictBase.getOther()");
expectedException.expectMessage(
"public abstract java.lang.Long org.apache.beam.sdk.options."
+ "PipelineOptionsFactoryTest$MultiReturnTypeConflict.getOther()");
PipelineOptionsFactory.as(MultiReturnTypeConflict.class);
}
/** Test interface that has {@link JsonIgnore @JsonIgnore} on a setter for a property. */
public interface SetterWithJsonIgnore extends PipelineOptions {
String getValue();
@JsonIgnore
void setValue(String value);
}
@Test
public void testSetterAnnotatedWithJsonIgnore() throws Exception {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
"Expected setter for property [value] to not be marked with @JsonIgnore on ["
+ "org.apache.beam.sdk.options.PipelineOptionsFactoryTest$SetterWithJsonIgnore]");
PipelineOptionsFactory.as(SetterWithJsonIgnore.class);
}
/** Test interface that has {@link JsonIgnore @JsonIgnore} on multiple setters. */
public interface MultiSetterWithJsonIgnore extends SetterWithJsonIgnore {
Integer getOther();
@JsonIgnore
void setOther(Integer other);
}
@Test
public void testMultipleSettersAnnotatedWithJsonIgnore() throws Exception {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Found setters marked with @JsonIgnore:");
expectedException.expectMessage(
"property [other] should not be marked with @JsonIgnore on ["
+ "org.apache.beam.sdk.options.PipelineOptionsFactoryTest$MultiSetterWithJsonIgnore]");
expectedException.expectMessage(
"property [value] should not be marked with @JsonIgnore on ["
+ "org.apache.beam.sdk.options.PipelineOptionsFactoryTest$SetterWithJsonIgnore]");
PipelineOptionsFactory.as(MultiSetterWithJsonIgnore.class);
}
/**
* This class is has a conflicting field with {@link CombinedObject} that doesn't have {@link
* JsonIgnore @JsonIgnore}.
*/
public interface GetterWithJsonIgnore extends PipelineOptions {
@JsonIgnore
Object getObject();
void setObject(Object value);
}
/**
* This class is has a conflicting {@link JsonIgnore @JsonIgnore} value with {@link
* GetterWithJsonIgnore}.
*/
public interface GetterWithInconsistentJsonIgnoreValue extends PipelineOptions {
@JsonIgnore(value = false)
Object getObject();
void setObject(Object value);
}
@Test
public void testNotAllGettersAnnotatedWithJsonIgnore() throws Exception {
// Initial construction is valid.
GetterWithJsonIgnore options = PipelineOptionsFactory.as(GetterWithJsonIgnore.class);
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
"Expected getter for property [object] to be marked with @JsonIgnore on all ["
+ "org.apache.beam.sdk.options.PipelineOptionsFactoryTest$GetterWithJsonIgnore, "
+ "org.apache.beam.sdk.options.PipelineOptionsFactoryTest$MissingSetter], "
+ "found only on [org.apache.beam.sdk.options."
+ "PipelineOptionsFactoryTest$GetterWithJsonIgnore]");
// When we attempt to convert, we should error at this moment.
options.as(CombinedObject.class);
}
/** Test interface. */
public interface MultiGetters extends PipelineOptions {
Object getObject();
void setObject(Object value);
@JsonIgnore
Integer getOther();
void setOther(Integer value);
Void getConsistent();
void setConsistent(Void consistent);
}
/** Test interface. */
public interface MultipleGettersWithInconsistentJsonIgnore extends PipelineOptions {
@JsonIgnore
Object getObject();
void setObject(Object value);
Integer getOther();
void setOther(Integer value);
Void getConsistent();
void setConsistent(Void consistent);
}
@Test
public void testMultipleGettersWithInconsistentJsonIgnore() {
// Initial construction is valid.
MultiGetters options = PipelineOptionsFactory.as(MultiGetters.class);
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Property getters are inconsistently marked with @JsonIgnore:");
expectedException.expectMessage("property [object] to be marked on all");
expectedException.expectMessage(
"found only on [org.apache.beam.sdk.options." + "PipelineOptionsFactoryTest$MultiGetters]");
expectedException.expectMessage("property [other] to be marked on all");
expectedException.expectMessage(
"found only on [org.apache.beam.sdk.options."
+ "PipelineOptionsFactoryTest$MultipleGettersWithInconsistentJsonIgnore]");
expectedException.expectMessage(
Matchers.anyOf(
containsString(
java.util.Arrays.toString(
new String[] {
"org.apache.beam.sdk.options."
+ "PipelineOptionsFactoryTest$MultipleGettersWithInconsistentJsonIgnore",
"org.apache.beam.sdk.options.PipelineOptionsFactoryTest$MultiGetters"
})),
containsString(
java.util.Arrays.toString(
new String[] {
"org.apache.beam.sdk.options.PipelineOptionsFactoryTest$MultiGetters",
"org.apache.beam.sdk.options."
+ "PipelineOptionsFactoryTest$MultipleGettersWithInconsistentJsonIgnore"
}))));
expectedException.expectMessage(not(containsString("property [consistent]")));
// When we attempt to convert, we should error immediately
options.as(MultipleGettersWithInconsistentJsonIgnore.class);
}
/** Test interface that has {@link Default @Default} on a setter for a property. */
public interface SetterWithDefault extends PipelineOptions {
String getValue();
@Default.String("abc")
void setValue(String value);
}
@Test
public void testSetterAnnotatedWithDefault() throws Exception {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
"Expected setter for property [value] to not be marked with @Default on ["
+ "org.apache.beam.sdk.options.PipelineOptionsFactoryTest$SetterWithDefault]");
PipelineOptionsFactory.as(SetterWithDefault.class);
}
/** Test interface that has {@link Default @Default} on multiple setters. */
public interface MultiSetterWithDefault extends SetterWithDefault {
Integer getOther();
@Default.String("abc")
void setOther(Integer other);
}
@Test
public void testMultipleSettersAnnotatedWithDefault() throws Exception {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Found setters marked with @Default:");
expectedException.expectMessage(
"property [other] should not be marked with @Default on ["
+ "org.apache.beam.sdk.options.PipelineOptionsFactoryTest$MultiSetterWithDefault]");
expectedException.expectMessage(
"property [value] should not be marked with @Default on ["
+ "org.apache.beam.sdk.options.PipelineOptionsFactoryTest$SetterWithDefault]");
PipelineOptionsFactory.as(MultiSetterWithDefault.class);
}
/**
* This class is has a conflicting field with {@link CombinedObject} that doesn't have {@link
* Default @Default}.
*/
public interface GetterWithDefault extends PipelineOptions {
@Default.Integer(1)
Object getObject();
void setObject(Object value);
}
/**
* This class is consistent with {@link GetterWithDefault} that has the same {@link
* Default @Default}.
*/
public interface GetterWithConsistentDefault extends PipelineOptions {
@Default.Integer(1)
Object getObject();
void setObject(Object value);
}
/**
* This class is inconsistent with {@link GetterWithDefault} that has a different {@link
* Default @Default}.
*/
public interface GetterWithInconsistentDefaultType extends PipelineOptions {
@Default.String("abc")
Object getObject();
void setObject(Object value);
}
/**
* This class is inconsistent with {@link GetterWithDefault} that has a different {@link
* Default @Default} value.
*/
public interface GetterWithInconsistentDefaultValue extends PipelineOptions {
@Default.Integer(0)
Object getObject();
void setObject(Object value);
}
@Test
public void testNotAllGettersAnnotatedWithDefault() throws Exception {
// Initial construction is valid.
GetterWithDefault options = PipelineOptionsFactory.as(GetterWithDefault.class);
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
"Expected getter for property [object] to be marked with @Default on all ["
+ "org.apache.beam.sdk.options.PipelineOptionsFactoryTest$GetterWithDefault, "
+ "org.apache.beam.sdk.options.PipelineOptionsFactoryTest$MissingSetter], "
+ "found only on [org.apache.beam.sdk.options."
+ "PipelineOptionsFactoryTest$GetterWithDefault]");
// When we attempt to convert, we should error at this moment.
options.as(CombinedObject.class);
}
@Test
public void testGettersAnnotatedWithConsistentDefault() throws Exception {
GetterWithConsistentDefault options =
PipelineOptionsFactory.as(GetterWithDefault.class).as(GetterWithConsistentDefault.class);
assertEquals(1, options.getObject());
}
@Test
public void testGettersAnnotatedWithInconsistentDefault() throws Exception {
// Initial construction is valid.
GetterWithDefault options = PipelineOptionsFactory.as(GetterWithDefault.class);
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
"Property [object] is marked with contradictory annotations. Found ["
+ "[Default.Integer(value=1) on org.apache.beam.sdk.options.PipelineOptionsFactoryTest"
+ "$GetterWithDefault#getObject()], "
+ "[Default.String(value=abc) on org.apache.beam.sdk.options.PipelineOptionsFactoryTest"
+ "$GetterWithInconsistentDefaultType#getObject()]].");
// When we attempt to convert, we should error at this moment.
options.as(GetterWithInconsistentDefaultType.class);
}
@Test
public void testGettersAnnotatedWithInconsistentDefaultValue() throws Exception {
// Initial construction is valid.
GetterWithDefault options = PipelineOptionsFactory.as(GetterWithDefault.class);
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
"Property [object] is marked with contradictory annotations. Found ["
+ "[Default.Integer(value=1) on org.apache.beam.sdk.options.PipelineOptionsFactoryTest"
+ "$GetterWithDefault#getObject()], "
+ "[Default.Integer(value=0) on org.apache.beam.sdk.options.PipelineOptionsFactoryTest"
+ "$GetterWithInconsistentDefaultValue#getObject()]].");
// When we attempt to convert, we should error at this moment.
options.as(GetterWithInconsistentDefaultValue.class);
}
@Test
public void testGettersAnnotatedWithInconsistentJsonIgnoreValue() throws Exception {
// Initial construction is valid.
GetterWithJsonIgnore options = PipelineOptionsFactory.as(GetterWithJsonIgnore.class);
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
"Property [object] is marked with contradictory annotations. Found ["
+ "[JsonIgnore(value=false) on org.apache.beam.sdk.options.PipelineOptionsFactoryTest"
+ "$GetterWithInconsistentJsonIgnoreValue#getObject()], "
+ "[JsonIgnore(value=true) on org.apache.beam.sdk.options.PipelineOptionsFactoryTest"
+ "$GetterWithJsonIgnore#getObject()]].");
// When we attempt to convert, we should error at this moment.
options.as(GetterWithInconsistentJsonIgnoreValue.class);
}
/** Test interface. */
public interface GettersWithMultipleDefault extends PipelineOptions {
@Default.String("abc")
@Default.Integer(0)
Object getObject();
void setObject(Object value);
}
@Test
public void testGettersWithMultipleDefaults() throws Exception {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
"Property [object] is marked with contradictory annotations. Found ["
+ "[Default.String(value=abc) on org.apache.beam.sdk.options.PipelineOptionsFactoryTest"
+ "$GettersWithMultipleDefault#getObject()], "
+ "[Default.Integer(value=0) on org.apache.beam.sdk.options.PipelineOptionsFactoryTest"
+ "$GettersWithMultipleDefault#getObject()]].");
// When we attempt to create, we should error at this moment.
PipelineOptionsFactory.as(GettersWithMultipleDefault.class);
}
/** Test interface. */
public interface MultiGettersWithDefault extends PipelineOptions {
Object getObject();
void setObject(Object value);
@Default.Integer(1)
Integer getOther();
void setOther(Integer value);
Void getConsistent();
void setConsistent(Void consistent);
}
/** Test interface. */
public interface MultipleGettersWithInconsistentDefault extends PipelineOptions {
@Default.Boolean(true)
Object getObject();
void setObject(Object value);
Integer getOther();
void setOther(Integer value);
Void getConsistent();
void setConsistent(Void consistent);
}
@Test
public void testMultipleGettersWithInconsistentDefault() {
// Initial construction is valid.
MultiGettersWithDefault options = PipelineOptionsFactory.as(MultiGettersWithDefault.class);
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Property getters are inconsistently marked with @Default:");
expectedException.expectMessage("property [object] to be marked on all");
expectedException.expectMessage(
"found only on [org.apache.beam.sdk.options."
+ "PipelineOptionsFactoryTest$MultiGettersWithDefault]");
expectedException.expectMessage("property [other] to be marked on all");
expectedException.expectMessage(
"found only on [org.apache.beam.sdk.options."
+ "PipelineOptionsFactoryTest$MultipleGettersWithInconsistentDefault]");
expectedException.expectMessage(
Matchers.anyOf(
containsString(
java.util.Arrays.toString(
new String[] {
"org.apache.beam.sdk.options."
+ "PipelineOptionsFactoryTest$MultipleGettersWithInconsistentDefault",
"org.apache.beam.sdk.options.PipelineOptionsFactoryTest$MultiGettersWithDefault"
})),
containsString(
java.util.Arrays.toString(
new String[] {
"org.apache.beam.sdk.options.PipelineOptionsFactoryTest$MultiGettersWithDefault",
"org.apache.beam.sdk.options."
+ "PipelineOptionsFactoryTest$MultipleGettersWithInconsistentDefault"
}))));
expectedException.expectMessage(not(containsString("property [consistent]")));
// When we attempt to convert, we should error immediately
options.as(MultipleGettersWithInconsistentDefault.class);
}
@Test
public void testAppNameIsNotOverriddenWhenPassedInViaCommandLine() {
ApplicationNameOptions options =
PipelineOptionsFactory.fromArgs("--appName=testAppName").as(ApplicationNameOptions.class);
assertEquals("testAppName", options.getAppName());
}
@Test
public void testPropertyIsSetOnRegisteredPipelineOptionNotPartOfOriginalInterface() {
PipelineOptions options = PipelineOptionsFactory.fromArgs("--streaming").create();
assertTrue(options.as(StreamingOptions.class).isStreaming());
}
/** A test interface containing all the primitives. */
public interface Primitives 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);
}
@Test
public void testPrimitives() {
String[] args =
new String[] {
"--boolean=true",
"--char=d",
"--byte=12",
"--short=300",
"--int=100000",
"--long=123890123890",
"--float=55.5",
"--double=12.3"
};
Primitives options = PipelineOptionsFactory.fromArgs(args).as(Primitives.class);
assertTrue(options.getBoolean());
assertEquals('d', options.getChar());
assertEquals((byte) 12, options.getByte());
assertEquals((short) 300, options.getShort());
assertEquals(100000, options.getInt());
assertEquals(123890123890L, options.getLong());
assertEquals(55.5f, options.getFloat(), 0.0f);
assertEquals(12.3, options.getDouble(), 0.0);
}
@Test
public void testBooleanShorthandArgument() {
String[] args = new String[] {"--boolean"};
Primitives options = PipelineOptionsFactory.fromArgs(args).as(Primitives.class);
assertTrue(options.getBoolean());
}
@Test
public void testEmptyValueNotAllowed() {
String[] args = new String[] {"--byte="};
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(emptyStringErrorMessage());
PipelineOptionsFactory.fromArgs(args).as(Primitives.class);
}
/** Enum used for testing PipelineOptions CLI parsing. */
public enum TestEnum {
Value,
Value2
}
/** A test interface containing all supported objects. */
public interface Objects extends PipelineOptions {
Boolean getBoolean();
void setBoolean(Boolean value);
Character getChar();
void setChar(Character value);
Byte getByte();
void setByte(Byte value);
Short getShort();
void setShort(Short value);
Integer getInt();
void setInt(Integer value);
Long getLong();
void setLong(Long value);
Float getFloat();
void setFloat(Float value);
Double getDouble();
void setDouble(Double value);
String getString();
void setString(String value);
String getEmptyString();
void setEmptyString(String value);
Class<?> getClassValue();
void setClassValue(Class<?> value);
TestEnum getEnum();
void setEnum(TestEnum value);
ValueProvider<String> getStringValue();
void setStringValue(ValueProvider<String> value);
ValueProvider<Long> getLongValue();
void setLongValue(ValueProvider<Long> value);
ValueProvider<TestEnum> getEnumValue();
void setEnumValue(ValueProvider<TestEnum> value);
}
@Test
public void testObjects() {
String[] args =
new String[] {
"--boolean=true",
"--char=d",
"--byte=12",
"--short=300",
"--int=100000",
"--long=123890123890",
"--float=55.5",
"--double=12.3",
"--string=stringValue",
"--emptyString=",
"--classValue=" + PipelineOptionsFactoryTest.class.getName(),
"--enum=" + TestEnum.Value,
"--stringValue=beam",
"--longValue=12389049585840",
"--enumValue=" + TestEnum.Value
};
Objects options = PipelineOptionsFactory.fromArgs(args).as(Objects.class);
assertTrue(options.getBoolean());
assertEquals(Character.valueOf('d'), options.getChar());
assertEquals(Byte.valueOf((byte) 12), options.getByte());
assertEquals(Short.valueOf((short) 300), options.getShort());
assertEquals(Integer.valueOf(100000), options.getInt());
assertEquals(Long.valueOf(123890123890L), options.getLong());
assertEquals(55.5f, options.getFloat(), 0.0f);
assertEquals(12.3, options.getDouble(), 0.0);
assertEquals("stringValue", options.getString());
assertTrue(options.getEmptyString().isEmpty());
assertEquals(PipelineOptionsFactoryTest.class, options.getClassValue());
assertEquals(TestEnum.Value, options.getEnum());
assertEquals("beam", options.getStringValue().get());
assertEquals(Long.valueOf(12389049585840L), options.getLongValue().get());
assertEquals(TestEnum.Value, options.getEnumValue().get());
}
@Test
public void testStringValueProvider() {
String[] args = new String[] {"--stringValue=beam"};
String[] emptyArgs = new String[] {"--stringValue="};
Objects options = PipelineOptionsFactory.fromArgs(args).as(Objects.class);
assertEquals("beam", options.getStringValue().get());
options = PipelineOptionsFactory.fromArgs(emptyArgs).as(Objects.class);
assertEquals("", options.getStringValue().get());
}
@Test
public void testLongValueProvider() {
String[] args = new String[] {"--longValue=12345678762"};
String[] emptyArgs = new String[] {"--longValue="};
Objects options = PipelineOptionsFactory.fromArgs(args).as(Objects.class);
assertEquals(Long.valueOf(12345678762L), options.getLongValue().get());
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(emptyStringErrorMessage());
PipelineOptionsFactory.fromArgs(emptyArgs).as(Objects.class);
}
@Test
public void testEnumValueProvider() {
String[] args = new String[] {"--enumValue=" + TestEnum.Value};
String[] emptyArgs = new String[] {"--enumValue="};
Objects options = PipelineOptionsFactory.fromArgs(args).as(Objects.class);
assertEquals(TestEnum.Value, options.getEnumValue().get());
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(emptyStringErrorMessage());
PipelineOptionsFactory.fromArgs(emptyArgs).as(Objects.class);
}
/** A test class for verifying JSON -> Object conversion. */
public static class ComplexType {
String value;
String value2;
public ComplexType(@JsonProperty("key") String value, @JsonProperty("key2") String value2) {
this.value = value;
this.value2 = value2;
}
}
/** A test interface for verifying JSON -> complex type conversion. */
public interface ComplexTypes extends PipelineOptions {
Map<String, String> getMap();
void setMap(Map<String, String> value);
ComplexType getObject();
void setObject(ComplexType value);
ValueProvider<ComplexType> getObjectValue();
void setObjectValue(ValueProvider<ComplexType> value);
}
@Test
public void testComplexTypes() {
String[] args =
new String[] {
"--map={\"key\":\"value\",\"key2\":\"value2\"}",
"--object={\"key\":\"value\",\"key2\":\"value2\"}",
"--objectValue={\"key\":\"value\",\"key2\":\"value2\"}"
};
ComplexTypes options = PipelineOptionsFactory.fromArgs(args).as(ComplexTypes.class);
assertEquals(ImmutableMap.of("key", "value", "key2", "value2"), options.getMap());
assertEquals("value", options.getObject().value);
assertEquals("value2", options.getObject().value2);
assertEquals("value", options.getObjectValue().get().value);
assertEquals("value2", options.getObjectValue().get().value2);
}
@Test
public void testMissingArgument() {
String[] args = new String[] {};
Objects options = PipelineOptionsFactory.fromArgs(args).as(Objects.class);
assertNull(options.getString());
}
/** A test interface containing all supported array return types. */
public interface Arrays extends PipelineOptions {
boolean[] getBoolean();
void setBoolean(boolean[] value);
char[] getChar();
void setChar(char[] 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);
String[] getString();
void setString(String[] value);
Class<?>[] getClassValue();
void setClassValue(Class<?>[] value);
TestEnum[] getEnum();
void setEnum(TestEnum[] value);
ValueProvider<String[]> getStringValue();
void setStringValue(ValueProvider<String[]> value);
ValueProvider<Long[]> getLongValue();
void setLongValue(ValueProvider<Long[]> value);
ValueProvider<TestEnum[]> getEnumValue();
void setEnumValue(ValueProvider<TestEnum[]> value);
}
@Test
@SuppressWarnings("rawtypes")
public void testArrays() {
String[] args =
new String[] {
"--boolean=true",
"--boolean=true",
"--boolean=false",
"--char=d",
"--char=e",
"--char=f",
"--short=300",
"--short=301",
"--short=302",
"--int=100000",
"--int=100001",
"--int=100002",
"--long=123890123890",
"--long=123890123891",
"--long=123890123892",
"--float=55.5",
"--float=55.6",
"--float=55.7",
"--double=12.3",
"--double=12.4",
"--double=12.5",
"--string=stringValue1",
"--string=stringValue2",
"--string=stringValue3",
"--classValue=" + PipelineOptionsFactory.class.getName(),
"--classValue=" + PipelineOptionsFactoryTest.class.getName(),
"--enum=" + TestEnum.Value,
"--enum=" + TestEnum.Value2,
"--stringValue=abc",
"--stringValue=beam",
"--longValue=123890123890",
"--longValue=123890123891",
"--enumValue=" + TestEnum.Value,
"--enumValue=" + TestEnum.Value2
};
Arrays options = PipelineOptionsFactory.fromArgs(args).as(Arrays.class);
boolean[] bools = options.getBoolean();
assertTrue(bools[0] && bools[1] && !bools[2]);
assertArrayEquals(new char[] {'d', 'e', 'f'}, options.getChar());
assertArrayEquals(new short[] {300, 301, 302}, options.getShort());
assertArrayEquals(new int[] {100000, 100001, 100002}, options.getInt());
assertArrayEquals(new long[] {123890123890L, 123890123891L, 123890123892L}, options.getLong());
assertArrayEquals(new float[] {55.5f, 55.6f, 55.7f}, options.getFloat(), 0.0f);
assertArrayEquals(new double[] {12.3, 12.4, 12.5}, options.getDouble(), 0.0);
assertArrayEquals(
new String[] {"stringValue1", "stringValue2", "stringValue3"}, options.getString());
assertArrayEquals(
new Class[] {PipelineOptionsFactory.class, PipelineOptionsFactoryTest.class},
options.getClassValue());
assertArrayEquals(new TestEnum[] {TestEnum.Value, TestEnum.Value2}, options.getEnum());
assertArrayEquals(new String[] {"abc", "beam"}, options.getStringValue().get());
assertArrayEquals(new Long[] {123890123890L, 123890123891L}, options.getLongValue().get());
assertArrayEquals(
new TestEnum[] {TestEnum.Value, TestEnum.Value2}, options.getEnumValue().get());
}
@Test
@SuppressWarnings("rawtypes")
public void testEmptyInStringArrays() {
String[] args = new String[] {"--string=", "--string=", "--string="};
Arrays options = PipelineOptionsFactory.fromArgs(args).as(Arrays.class);
assertArrayEquals(new String[] {"", "", ""}, options.getString());
}
@Test
@SuppressWarnings("rawtypes")
public void testEmptyInStringArraysWithCommaList() {
String[] args = new String[] {"--string=a,,b"};
Arrays options = PipelineOptionsFactory.fromArgs(args).as(Arrays.class);
assertArrayEquals(new String[] {"a", "", "b"}, options.getString());
}
@Test
public void testEmptyInNonStringArrays() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(emptyStringErrorMessage());
String[] args = new String[] {"--boolean=true", "--boolean=", "--boolean=false"};
PipelineOptionsFactory.fromArgs(args).as(Arrays.class);
}
@Test
public void testEmptyInNonStringArraysWithCommaList() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(emptyStringErrorMessage());
String[] args = new String[] {"--int=1,,9"};
PipelineOptionsFactory.fromArgs(args).as(Arrays.class);
}
@Test
public void testStringArrayValueProvider() {
String[] args = new String[] {"--stringValue=abc", "--stringValue=xyz"};
String[] commaArgs = new String[] {"--stringValue=abc,xyz"};
String[] emptyArgs = new String[] {"--stringValue=", "--stringValue="};
Arrays options = PipelineOptionsFactory.fromArgs(args).as(Arrays.class);
assertArrayEquals(new String[] {"abc", "xyz"}, options.getStringValue().get());
options = PipelineOptionsFactory.fromArgs(commaArgs).as(Arrays.class);
assertArrayEquals(new String[] {"abc", "xyz"}, options.getStringValue().get());
options = PipelineOptionsFactory.fromArgs(emptyArgs).as(Arrays.class);
assertArrayEquals(new String[] {"", ""}, options.getStringValue().get());
}
@Test
public void testLongArrayValueProvider() {
String[] args = new String[] {"--longValue=12345678762", "--longValue=12345678763"};
String[] commaArgs = new String[] {"--longValue=12345678762,12345678763"};
String[] emptyArgs = new String[] {"--longValue=", "--longValue="};
Arrays options = PipelineOptionsFactory.fromArgs(args).as(Arrays.class);
assertArrayEquals(new Long[] {12345678762L, 12345678763L}, options.getLongValue().get());
options = PipelineOptionsFactory.fromArgs(commaArgs).as(Arrays.class);
assertArrayEquals(new Long[] {12345678762L, 12345678763L}, options.getLongValue().get());
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(emptyStringErrorMessage());
PipelineOptionsFactory.fromArgs(emptyArgs).as(Arrays.class);
}
@Test
public void testEnumArrayValueProvider() {
String[] args =
new String[] {"--enumValue=" + TestEnum.Value, "--enumValue=" + TestEnum.Value2};
String[] commaArgs = new String[] {"--enumValue=" + TestEnum.Value + "," + TestEnum.Value2};
String[] emptyArgs = new String[] {"--enumValue="};
Arrays options = PipelineOptionsFactory.fromArgs(args).as(Arrays.class);
assertArrayEquals(
new TestEnum[] {TestEnum.Value, TestEnum.Value2}, options.getEnumValue().get());
options = PipelineOptionsFactory.fromArgs(commaArgs).as(Arrays.class);
assertArrayEquals(
new TestEnum[] {TestEnum.Value, TestEnum.Value2}, options.getEnumValue().get());
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(emptyStringErrorMessage());
PipelineOptionsFactory.fromArgs(emptyArgs).as(Arrays.class);
}
@Test
public void testOutOfOrderArrays() {
String[] args =
new String[] {
"--char=d", "--boolean=true", "--boolean=true", "--char=e", "--char=f", "--boolean=false"
};
Arrays options = PipelineOptionsFactory.fromArgs(args).as(Arrays.class);
boolean[] bools = options.getBoolean();
assertTrue(bools[0] && bools[1] && !bools[2]);
assertArrayEquals(new char[] {'d', 'e', 'f'}, options.getChar());
}
/** A test interface containing all supported List return types. */
public interface Lists extends PipelineOptions {
List<String> getString();
void setString(List<String> value);
List<Integer> getInteger();
void setInteger(List<Integer> value);
@SuppressWarnings("rawtypes")
List getList();
@SuppressWarnings("rawtypes")
void setList(List value);
ValueProvider<List<String>> getStringValue();
void setStringValue(ValueProvider<List<String>> value);
ValueProvider<List<Long>> getLongValue();
void setLongValue(ValueProvider<List<Long>> value);
ValueProvider<List<TestEnum>> getEnumValue();
void setEnumValue(ValueProvider<List<TestEnum>> value);
}
@Test
public void testListRawDefaultsToString() {
String[] manyArgs =
new String[] {"--list=stringValue1", "--list=stringValue2", "--list=stringValue3"};
String[] manyArgsWithEmptyString =
new String[] {"--list=stringValue1", "--list=", "--list=stringValue3"};
Lists options = PipelineOptionsFactory.fromArgs(manyArgs).as(Lists.class);
assertEquals(
ImmutableList.of("stringValue1", "stringValue2", "stringValue3"), options.getList());
options = PipelineOptionsFactory.fromArgs(manyArgsWithEmptyString).as(Lists.class);
assertEquals(ImmutableList.of("stringValue1", "", "stringValue3"), options.getList());
}
@Test
public void testListString() {
String[] manyArgs =
new String[] {"--string=stringValue1", "--string=stringValue2", "--string=stringValue3"};
String[] oneArg = new String[] {"--string=stringValue1"};
String[] emptyArg = new String[] {"--string="};
Lists options = PipelineOptionsFactory.fromArgs(manyArgs).as(Lists.class);
assertEquals(
ImmutableList.of("stringValue1", "stringValue2", "stringValue3"), options.getString());
options = PipelineOptionsFactory.fromArgs(oneArg).as(Lists.class);
assertEquals(ImmutableList.of("stringValue1"), options.getString());
options = PipelineOptionsFactory.fromArgs(emptyArg).as(Lists.class);
assertEquals(ImmutableList.of(""), options.getString());
}
@Test
public void testListInt() {
String[] manyArgs = new String[] {"--integer=1", "--integer=2", "--integer=3"};
String[] manyArgsShort = new String[] {"--integer=1,2,3"};
String[] oneArg = new String[] {"--integer=1"};
String[] missingArg = new String[] {"--integer="};
Lists options = PipelineOptionsFactory.fromArgs(manyArgs).as(Lists.class);
assertEquals(ImmutableList.of(1, 2, 3), options.getInteger());
options = PipelineOptionsFactory.fromArgs(manyArgsShort).as(Lists.class);
assertEquals(ImmutableList.of(1, 2, 3), options.getInteger());
options = PipelineOptionsFactory.fromArgs(oneArg).as(Lists.class);
assertEquals(ImmutableList.of(1), options.getInteger());
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(emptyStringErrorMessage("java.util.List<java.lang.Integer>"));
PipelineOptionsFactory.fromArgs(missingArg).as(Lists.class);
}
@Test
public void testListShorthand() {
String[] args = new String[] {"--string=stringValue1,stringValue2,stringValue3"};
Lists options = PipelineOptionsFactory.fromArgs(args).as(Lists.class);
assertEquals(
ImmutableList.of("stringValue1", "stringValue2", "stringValue3"), options.getString());
}
@Test
public void testMixedShorthandAndLongStyleList() {
String[] args =
new String[] {
"--char=d",
"--char=e",
"--char=f",
"--char=g,h,i",
"--char=j",
"--char=k",
"--char=l",
"--char=m,n,o"
};
Arrays options = PipelineOptionsFactory.fromArgs(args).as(Arrays.class);
assertArrayEquals(
new char[] {'d', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o'}, options.getChar());
}
@Test
public void testStringListValueProvider() {
String[] args = new String[] {"--stringValue=abc", "--stringValue=xyz"};
String[] commaArgs = new String[] {"--stringValue=abc,xyz"};
String[] emptyArgs = new String[] {"--stringValue=", "--stringValue="};
Lists options = PipelineOptionsFactory.fromArgs(args).as(Lists.class);
assertEquals(ImmutableList.of("abc", "xyz"), options.getStringValue().get());
options = PipelineOptionsFactory.fromArgs(commaArgs).as(Lists.class);
assertEquals(ImmutableList.of("abc", "xyz"), options.getStringValue().get());
options = PipelineOptionsFactory.fromArgs(emptyArgs).as(Lists.class);
assertEquals(ImmutableList.of("", ""), options.getStringValue().get());
}
@Test
public void testLongListValueProvider() {
String[] args = new String[] {"--longValue=12345678762", "--longValue=12345678763"};
String[] commaArgs = new String[] {"--longValue=12345678762,12345678763"};
String[] emptyArgs = new String[] {"--longValue=", "--longValue="};
Lists options = PipelineOptionsFactory.fromArgs(args).as(Lists.class);
assertEquals(ImmutableList.of(12345678762L, 12345678763L), options.getLongValue().get());
options = PipelineOptionsFactory.fromArgs(commaArgs).as(Lists.class);
assertEquals(ImmutableList.of(12345678762L, 12345678763L), options.getLongValue().get());
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(emptyStringErrorMessage());
PipelineOptionsFactory.fromArgs(emptyArgs).as(Lists.class);
}
@Test
public void testEnumListValueProvider() {
String[] args =
new String[] {"--enumValue=" + TestEnum.Value, "--enumValue=" + TestEnum.Value2};
String[] commaArgs = new String[] {"--enumValue=" + TestEnum.Value + "," + TestEnum.Value2};
String[] emptyArgs = new String[] {"--enumValue="};
Lists options = PipelineOptionsFactory.fromArgs(args).as(Lists.class);
assertEquals(ImmutableList.of(TestEnum.Value, TestEnum.Value2), options.getEnumValue().get());
options = PipelineOptionsFactory.fromArgs(commaArgs).as(Lists.class);
assertEquals(ImmutableList.of(TestEnum.Value, TestEnum.Value2), options.getEnumValue().get());
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(emptyStringErrorMessage());
PipelineOptionsFactory.fromArgs(emptyArgs).as(Lists.class);
}
@Test
public void testSetASingularAttributeUsingAListThrowsAnError() {
String[] args = new String[] {"--string=100", "--string=200"};
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("expected one element but was");
PipelineOptionsFactory.fromArgs(args).as(Objects.class);
}
@Test
public void testSetASingularAttributeUsingAListIsIgnoredWithoutStrictParsing() {
String[] args = new String[] {"--diskSizeGb=100", "--diskSizeGb=200"};
PipelineOptionsFactory.fromArgs(args).withoutStrictParsing().create();
expectedLogs.verifyWarn("Strict parsing is disabled, ignoring option");
}
private interface NonPublicPipelineOptions extends PipelineOptions {}
@Test
public void testNonPublicInterfaceThrowsException() throws Exception {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
"Please mark non-public interface "
+ NonPublicPipelineOptions.class.getName()
+ " as public.");
PipelineOptionsFactory.as(NonPublicPipelineOptions.class);
}
/** A test interface containing all supported List return types. */
public interface Maps extends PipelineOptions {
Map<Integer, Integer> getMap();
void setMap(Map<Integer, Integer> value);
Map<Integer, Map<Integer, Integer>> getNestedMap();
void setNestedMap(Map<Integer, Map<Integer, Integer>> value);
}
@Test
public void testMapIntInt() {
String[] manyArgsShort = new String[] {"--map={\"1\":1,\"2\":2}"};
String[] oneArg = new String[] {"--map={\"1\":1}"};
String[] missingArg = new String[] {"--map="};
Maps options = PipelineOptionsFactory.fromArgs(manyArgsShort).as(Maps.class);
assertEquals(ImmutableMap.of(1, 1, 2, 2), options.getMap());
options = PipelineOptionsFactory.fromArgs(oneArg).as(Maps.class);
assertEquals(ImmutableMap.of(1, 1), options.getMap());
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
emptyStringErrorMessage("java.util.Map<java.lang.Integer, java.lang.Integer>"));
PipelineOptionsFactory.fromArgs(missingArg).as(Maps.class);
}
@Test
public void testNestedMap() {
String[] manyArgsShort = new String[] {"--nestedMap={\"1\":{\"1\":1},\"2\":{\"2\":2}}"};
String[] oneArg = new String[] {"--nestedMap={\"1\":{\"1\":1}}"};
String[] missingArg = new String[] {"--nestedMap="};
Maps options = PipelineOptionsFactory.fromArgs(manyArgsShort).as(Maps.class);
assertEquals(
ImmutableMap.of(
1, ImmutableMap.of(1, 1),
2, ImmutableMap.of(2, 2)),
options.getNestedMap());
options = PipelineOptionsFactory.fromArgs(oneArg).as(Maps.class);
assertEquals(ImmutableMap.of(1, ImmutableMap.of(1, 1)), options.getNestedMap());
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
emptyStringErrorMessage(
"java.util.Map<java.lang.Integer, java.util.Map<java.lang.Integer, java.lang.Integer>>"));
PipelineOptionsFactory.fromArgs(missingArg).as(Maps.class);
}
@Test
public void testSettingRunner() {
String[] args = new String[] {"--runner=" + RegisteredTestRunner.class.getSimpleName()};
PipelineOptions options = PipelineOptionsFactory.fromArgs(args).create();
assertEquals(RegisteredTestRunner.class, options.getRunner());
}
@Test
public void testSettingRunnerFullName() {
String[] args = new String[] {String.format("--runner=%s", CrashingRunner.class.getName())};
PipelineOptions opts = PipelineOptionsFactory.fromArgs(args).create();
assertEquals(opts.getRunner(), CrashingRunner.class);
}
@Test
public void testSettingUnknownRunner() {
String[] args = new String[] {"--runner=UnknownRunner"};
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
"Unknown 'runner' specified 'UnknownRunner', supported " + "pipeline runners");
Set<String> registeredRunners = PipelineOptionsFactory.getRegisteredRunners().keySet();
assertThat(registeredRunners, hasItem(REGISTERED_RUNNER.getSimpleName().toLowerCase()));
expectedException.expectMessage(
PipelineOptionsFactory.CACHE.get().getSupportedRunners().toString());
PipelineOptionsFactory.fromArgs(args).create();
}
private static class ExampleTestRunner extends PipelineRunner<PipelineResult> {
@Override
public PipelineResult run(Pipeline pipeline) {
return null;
}
}
@Test
public void testSettingRunnerCanonicalClassNameNotInSupportedExists() {
String[] args = new String[] {String.format("--runner=%s", ExampleTestRunner.class.getName())};
PipelineOptions opts = PipelineOptionsFactory.fromArgs(args).create();
assertEquals(opts.getRunner(), ExampleTestRunner.class);
}
@Test
public void testSettingRunnerCanonicalClassNameNotInSupportedNotPipelineRunner() {
String[] args = new String[] {"--runner=java.lang.String"};
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("does not implement PipelineRunner");
expectedException.expectMessage("java.lang.String");
PipelineOptionsFactory.fromArgs(args).create();
}
@Test
public void testUsingArgumentWithUnknownPropertyIsNotAllowed() {
String[] args = new String[] {"--unknownProperty=value"};
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("missing a property named 'unknownProperty'");
PipelineOptionsFactory.fromArgs(args).create();
}
/** Test interface. */
public interface SuggestedOptions extends PipelineOptions {
String getAbc();
void setAbc(String value);
String getAbcdefg();
void setAbcdefg(String value);
}
@Test
public void testUsingArgumentWithMisspelledPropertyGivesASuggestion() {
String[] args = new String[] {"--ab=value"};
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("missing a property named 'ab'. Did you mean 'abc'?");
PipelineOptionsFactory.fromArgs(args).as(SuggestedOptions.class);
}
@Test
public void testUsingArgumentWithMisspelledPropertyGivesMultipleSuggestions() {
String[] args = new String[] {"--abcde=value"};
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
"missing a property named 'abcde'. Did you mean one of [abc, abcdefg]?");
PipelineOptionsFactory.fromArgs(args).as(SuggestedOptions.class);
}
@Test
public void testUsingArgumentWithUnknownPropertyIsIgnoredWithoutStrictParsing() {
String[] args = new String[] {"--unknownProperty=value"};
PipelineOptionsFactory.fromArgs(args).withoutStrictParsing().create();
expectedLogs.verifyWarn("missing a property named 'unknownProperty'");
}
@Test
public void testUsingArgumentStartingWithIllegalCharacterIsNotAllowed() {
String[] args = new String[] {" --diskSizeGb=100"};
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Argument ' --diskSizeGb=100' does not begin with '--'");
PipelineOptionsFactory.fromArgs(args).create();
}
@Test
public void testUsingArgumentStartingWithIllegalCharacterIsIgnoredWithoutStrictParsing() {
String[] args = new String[] {" --diskSizeGb=100"};
PipelineOptionsFactory.fromArgs(args).withoutStrictParsing().create();
expectedLogs.verifyWarn("Strict parsing is disabled, ignoring option");
}
@Test
public void testEmptyArgumentIsIgnored() {
String[] args =
new String[] {"", "--string=100", "", "", "--runner=" + REGISTERED_RUNNER.getSimpleName()};
PipelineOptionsFactory.fromArgs(args).as(Objects.class);
}
@Test
public void testNullArgumentIsIgnored() {
String[] args =
new String[] {"--string=100", null, null, "--runner=" + REGISTERED_RUNNER.getSimpleName()};
PipelineOptionsFactory.fromArgs(args).as(Objects.class);
}
@Test
public void testUsingArgumentWithInvalidNameIsNotAllowed() {
String[] args = new String[] {"--=100"};
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Argument '--=100' starts with '--='");
PipelineOptionsFactory.fromArgs(args).create();
}
@Test
public void testUsingArgumentWithInvalidNameIsIgnoredWithoutStrictParsing() {
String[] args = new String[] {"--=100"};
PipelineOptionsFactory.fromArgs(args).withoutStrictParsing().create();
expectedLogs.verifyWarn("Strict parsing is disabled, ignoring option");
}
@Test
public void testWhenNoHelpIsRequested() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ListMultimap<String, String> arguments = ArrayListMultimap.create();
assertFalse(
PipelineOptionsFactory.printHelpUsageAndExitIfNeeded(
arguments, new PrintStream(baos), false /* exit */));
String output = new String(baos.toByteArray(), Charsets.UTF_8);
assertEquals("", output);
}
@Test
public void testDefaultHelpAsArgument() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ListMultimap<String, String> arguments = ArrayListMultimap.create();
arguments.put("help", "true");
assertTrue(
PipelineOptionsFactory.printHelpUsageAndExitIfNeeded(
arguments, new PrintStream(baos), false /* exit */));
String output = new String(baos.toByteArray(), Charsets.UTF_8);
assertThat(output, containsString("The set of registered options are:"));
assertThat(output, containsString("org.apache.beam.sdk.options.PipelineOptions"));
assertThat(output, containsString("Use --help=<OptionsName> for detailed help."));
}
@Test
public void testSpecificHelpAsArgument() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ListMultimap<String, String> arguments = ArrayListMultimap.create();
arguments.put("help", "org.apache.beam.sdk.options.PipelineOptions");
assertTrue(
PipelineOptionsFactory.printHelpUsageAndExitIfNeeded(
arguments, new PrintStream(baos), false /* exit */));
String output = new String(baos.toByteArray(), Charsets.UTF_8);
assertThat(output, containsString("org.apache.beam.sdk.options.PipelineOptions"));
assertThat(output, containsString("--runner"));
assertThat(output, containsString("Default: " + DEFAULT_RUNNER_NAME));
assertThat(
output, containsString("The pipeline runner that will be used to execute the pipeline."));
}
@Test
public void testSpecificHelpAsArgumentWithSimpleClassName() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ListMultimap<String, String> arguments = ArrayListMultimap.create();
arguments.put("help", "PipelineOptions");
assertTrue(
PipelineOptionsFactory.printHelpUsageAndExitIfNeeded(
arguments, new PrintStream(baos), false /* exit */));
String output = new String(baos.toByteArray(), Charsets.UTF_8);
assertThat(output, containsString("org.apache.beam.sdk.options.PipelineOptions"));
assertThat(output, containsString("--runner"));
assertThat(output, containsString("Default: " + DEFAULT_RUNNER_NAME));
assertThat(
output, containsString("The pipeline runner that will be used to execute the pipeline."));
}
@Test
public void testSpecificHelpAsArgumentWithClassNameSuffix() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ListMultimap<String, String> arguments = ArrayListMultimap.create();
arguments.put("help", "options.PipelineOptions");
assertTrue(
PipelineOptionsFactory.printHelpUsageAndExitIfNeeded(
arguments, new PrintStream(baos), false /* exit */));
String output = new String(baos.toByteArray(), Charsets.UTF_8);
assertThat(output, containsString("org.apache.beam.sdk.options.PipelineOptions"));
assertThat(output, containsString("--runner"));
assertThat(output, containsString("Default: " + DEFAULT_RUNNER_NAME));
assertThat(
output, containsString("The pipeline runner that will be used to execute the pipeline."));
}
/** Used for a name collision test with the other NameConflict interfaces. */
public static class NameConflictClassA {
/** Used for a name collision test with the other NameConflict interfaces. */
public interface NameConflict extends PipelineOptions {}
}
/** Used for a name collision test with the other NameConflict interfaces. */
public static class NameConflictClassB {
/** Used for a name collision test with the other NameConflict interfaces. */
public interface NameConflict extends PipelineOptions {}
}
@Test
public void testShortnameSpecificHelpHasMultipleMatches() {
PipelineOptionsFactory.register(NameConflictClassA.NameConflict.class);
PipelineOptionsFactory.register(NameConflictClassB.NameConflict.class);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ListMultimap<String, String> arguments = ArrayListMultimap.create();
arguments.put("help", "NameConflict");
assertTrue(
PipelineOptionsFactory.printHelpUsageAndExitIfNeeded(
arguments, new PrintStream(baos), false /* exit */));
String output = new String(baos.toByteArray(), Charsets.UTF_8);
assertThat(output, containsString("Multiple matches found for NameConflict"));
assertThat(
output,
containsString(
"org.apache.beam.sdk.options."
+ "PipelineOptionsFactoryTest$NameConflictClassA$NameConflict"));
assertThat(
output,
containsString(
"org.apache.beam.sdk.options."
+ "PipelineOptionsFactoryTest$NameConflictClassB$NameConflict"));
assertThat(output, containsString("The set of registered options are:"));
assertThat(output, containsString("org.apache.beam.sdk.options.PipelineOptions"));
}
@Test
public void testHelpWithOptionThatOutputsValidEnumTypes() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ListMultimap<String, String> arguments = ArrayListMultimap.create();
arguments.put("help", Objects.class.getName());
assertTrue(
PipelineOptionsFactory.printHelpUsageAndExitIfNeeded(
arguments, new PrintStream(baos), false /* exit */));
String output = new String(baos.toByteArray(), Charsets.UTF_8);
assertThat(output, containsString("<Value | Value2>"));
}
@Test
public void testHelpWithBadOptionNameAsArgument() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ListMultimap<String, String> arguments = ArrayListMultimap.create();
arguments.put("help", "org.apache.beam.sdk.Pipeline");
assertTrue(
PipelineOptionsFactory.printHelpUsageAndExitIfNeeded(
arguments, new PrintStream(baos), false /* exit */));
String output = new String(baos.toByteArray(), Charsets.UTF_8);
assertThat(output, containsString("Unable to find option org.apache.beam.sdk.Pipeline"));
assertThat(output, containsString("The set of registered options are:"));
assertThat(output, containsString("org.apache.beam.sdk.options.PipelineOptions"));
}
@Test
public void testHelpWithHiddenMethodAndInterface() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ListMultimap<String, String> arguments = ArrayListMultimap.create();
arguments.put("help", "org.apache.beam.sdk.option.DataflowPipelineOptions");
assertTrue(
PipelineOptionsFactory.printHelpUsageAndExitIfNeeded(
arguments, new PrintStream(baos), false /* exit */));
String output = new String(baos.toByteArray(), Charsets.UTF_8);
// A hidden interface.
assertThat(
output, not(containsString("org.apache.beam.sdk.options.DataflowPipelineDebugOptions")));
// A hidden option.
assertThat(output, not(containsString("--gcpCredential")));
}
@Test
public void testProgrammaticPrintHelp() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PipelineOptionsFactory.printHelp(new PrintStream(baos));
String output = new String(baos.toByteArray(), Charsets.UTF_8);
assertThat(output, containsString("The set of registered options are:"));
assertThat(output, containsString("org.apache.beam.sdk.options.PipelineOptions"));
}
@Test
public void testProgrammaticPrintHelpForSpecificType() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PipelineOptionsFactory.printHelp(new PrintStream(baos), PipelineOptions.class);
String output = new String(baos.toByteArray(), Charsets.UTF_8);
assertThat(output, containsString("org.apache.beam.sdk.options.PipelineOptions"));
assertThat(output, containsString("--runner"));
assertThat(output, containsString("Default: " + DEFAULT_RUNNER_NAME));
assertThat(
output, containsString("The pipeline runner that will be used to execute the pipeline."));
}
/** Test interface. */
public interface PipelineOptionsInheritedInvalid
extends Invalid1, InvalidPipelineOptions2, PipelineOptions {
String getFoo();
void setFoo(String value);
}
/** Test interface. */
public interface InvalidPipelineOptions1 {
String getBar();
void setBar(String value);
}
/** Test interface. */
public interface Invalid1 extends InvalidPipelineOptions1 {
@Override
String getBar();
@Override
void setBar(String value);
}
/** Test interface. */
public interface InvalidPipelineOptions2 {
String getBar();
void setBar(String value);
}
@Test
public void testAllFromPipelineOptions() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
"All inherited interfaces of [org.apache.beam.sdk.options.PipelineOptionsFactoryTest"
+ "$PipelineOptionsInheritedInvalid] should inherit from the PipelineOptions interface. "
+ "The following inherited interfaces do not:\n"
+ " - org.apache.beam.sdk.options.PipelineOptionsFactoryTest"
+ "$InvalidPipelineOptions1\n"
+ " - org.apache.beam.sdk.options.PipelineOptionsFactoryTest"
+ "$InvalidPipelineOptions2");
PipelineOptionsFactory.as(PipelineOptionsInheritedInvalid.class);
}
private String emptyStringErrorMessage() {
return emptyStringErrorMessage(null);
}
private String emptyStringErrorMessage(String type) {
String msg =
"Empty argument value is only allowed for String, String Array, "
+ "Collections of Strings or any of these types in a parameterized ValueProvider";
if (type != null) {
return String.format("%s, but received: %s", msg, type);
} else {
return msg;
}
}
private static class RegisteredTestRunner extends PipelineRunner<PipelineResult> {
public static PipelineRunner<PipelineResult> fromOptions(PipelineOptions options) {
return new RegisteredTestRunner();
}
@Override
public PipelineResult run(Pipeline p) {
throw new IllegalArgumentException();
}
}
/**
* A {@link PipelineRunnerRegistrar} to demonstrate default {@link PipelineRunner} registration.
*/
@AutoService(PipelineRunnerRegistrar.class)
public static class RegisteredTestRunnerRegistrar implements PipelineRunnerRegistrar {
@Override
public Iterable<Class<? extends PipelineRunner<?>>> getPipelineRunners() {
return ImmutableList.of(RegisteredTestRunner.class);
}
}
/** Test interface. */
public interface RegisteredTestOptions extends PipelineOptions {
Object getRegisteredExampleFooBar();
void setRegisteredExampleFooBar(Object registeredExampleFooBar);
}
/**
* A {@link PipelineOptionsRegistrar} to demonstrate default {@link PipelineOptions} registration.
*/
@AutoService(PipelineOptionsRegistrar.class)
public static class RegisteredTestOptionsRegistrar implements PipelineOptionsRegistrar {
@Override
public Iterable<Class<? extends PipelineOptions>> getPipelineOptions() {
return ImmutableList.of(RegisteredTestOptions.class);
}
}
@Test
public void testRegistrationOfJacksonModulesForObjectMapper() throws Exception {
JacksonIncompatibleOptions options =
PipelineOptionsFactory.fromArgs("--jacksonIncompatible=\"testValue\"")
.as(JacksonIncompatibleOptions.class);
assertEquals("testValue", options.getJacksonIncompatible().value);
}
/** PipelineOptions used to test auto registration of Jackson modules. */
public interface JacksonIncompatibleOptions extends PipelineOptions {
JacksonIncompatible getJacksonIncompatible();
void setJacksonIncompatible(JacksonIncompatible value);
}
/** A Jackson {@link Module} to test auto-registration of modules. */
@AutoService(Module.class)
public static class RegisteredTestModule extends SimpleModule {
public RegisteredTestModule() {
super("RegisteredTestModule");
setMixInAnnotation(JacksonIncompatible.class, JacksonIncompatibleMixin.class);
}
}
/** A class which Jackson does not know how to serialize/deserialize. */
public static class JacksonIncompatible {
private final String value;
public JacksonIncompatible(String value) {
this.value = value;
}
}
/** A Jackson mixin used to add annotations to other classes. */
@JsonDeserialize(using = JacksonIncompatibleDeserializer.class)
@JsonSerialize(using = JacksonIncompatibleSerializer.class)
public static final class JacksonIncompatibleMixin {}
/** A Jackson deserializer for {@link JacksonIncompatible}. */
public static class JacksonIncompatibleDeserializer
extends JsonDeserializer<JacksonIncompatible> {
@Override
public JacksonIncompatible deserialize(
JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException, JsonProcessingException {
return new JacksonIncompatible(jsonParser.readValueAs(String.class));
}
}
/** A Jackson serializer for {@link JacksonIncompatible}. */
public static class JacksonIncompatibleSerializer extends JsonSerializer<JacksonIncompatible> {
@Override
public void serialize(
JacksonIncompatible jacksonIncompatible,
JsonGenerator jsonGenerator,
SerializerProvider serializerProvider)
throws IOException, JsonProcessingException {
jsonGenerator.writeString(jacksonIncompatible.value);
}
}
/** Used to test that the thread context class loader is used when creating proxies. */
public interface ClassLoaderTestOptions extends PipelineOptions {
@Default.Boolean(true)
@Description("A test option.")
boolean isOption();
void setOption(boolean b);
}
@Test
public void testPipelineOptionsFactoryUsesTccl() throws Exception {
final Thread thread = Thread.currentThread();
final ClassLoader testClassLoader = thread.getContextClassLoader();
final ClassLoader caseLoader =
new InterceptingUrlClassLoader(
testClassLoader, name -> name.toLowerCase(ROOT).contains("test"));
thread.setContextClassLoader(caseLoader);
PipelineOptionsFactory.resetCache();
try {
final PipelineOptions pipelineOptions = PipelineOptionsFactory.create();
final Class optionType =
caseLoader.loadClass(
"org.apache.beam.sdk.options.PipelineOptionsFactoryTest$ClassLoaderTestOptions");
final Object options = pipelineOptions.as(optionType);
assertSame(caseLoader, options.getClass().getClassLoader());
assertSame(optionType.getClassLoader(), options.getClass().getClassLoader());
assertSame(testClassLoader, optionType.getInterfaces()[0].getClassLoader());
assertTrue(Boolean.class.cast(optionType.getMethod("isOption").invoke(options)));
} finally {
thread.setContextClassLoader(testClassLoader);
PipelineOptionsFactory.resetCache();
}
}
@Test
public void testDefaultMethodIgnoresDefaultImplementation() {
OptionsWithDefaultMethod optsWithDefault =
PipelineOptionsFactory.as(OptionsWithDefaultMethod.class);
assertThat(optsWithDefault.getValue(), nullValue());
optsWithDefault.setValue(12.25);
assertThat(optsWithDefault.getValue(), equalTo(12.25));
}
/** Test interface. */
public interface ExtendedOptionsWithDefault extends OptionsWithDefaultMethod {}
@Test
public void testDefaultMethodInExtendedClassIgnoresDefaultImplementation() {
OptionsWithDefaultMethod extendedOptsWithDefault =
PipelineOptionsFactory.as(ExtendedOptionsWithDefault.class);
assertThat(extendedOptsWithDefault.getValue(), nullValue());
extendedOptsWithDefault.setValue(Double.NEGATIVE_INFINITY);
assertThat(extendedOptsWithDefault.getValue(), equalTo(Double.NEGATIVE_INFINITY));
}
/** Test interface. */
public interface OptionsWithDefaultMethod extends PipelineOptions {
default Number getValue() {
return 1024;
}
void setValue(Number value);
}
@Test
public void testStaticMethodsAreAllowed() {
assertEquals(
"value",
OptionsWithStaticMethod.myStaticMethod(
PipelineOptionsFactory.fromArgs("--myMethod=value").as(OptionsWithStaticMethod.class)));
}
/** Test interface. */
public interface OptionsWithStaticMethod extends PipelineOptions {
String getMyMethod();
void setMyMethod(String value);
static String myStaticMethod(OptionsWithStaticMethod o) {
return o.getMyMethod();
}
}
/** Test interface. */
public interface TestDescribeOptions extends PipelineOptions {
String getString();
void setString(String value);
@Description("integer property")
Integer getInteger();
void setInteger(Integer value);
@Description("float number property")
Float getFloat();
void setFloat(Float value);
@Description("simple boolean property")
@Default.Boolean(true)
boolean getBooleanSimple();
void setBooleanSimple(boolean value);
@Default.Boolean(false)
Boolean getBooleanWrapper();
void setBooleanWrapper(Boolean value);
List<Integer> getList();
void setList(List<Integer> value);
}
@Test
public void testDescribe() {
List<PipelineOptionDescriptor> described =
PipelineOptionsFactory.describe(
Sets.newHashSet(PipelineOptions.class, TestDescribeOptions.class));
Map<String, PipelineOptionDescriptor> mapped = uniqueIndex(described, input -> input.getName());
assertEquals("no duplicates", described.size(), mapped.size());
Collection<PipelineOptionDescriptor> filtered =
Collections2.filter(
described, input -> input.getGroup().equals(TestDescribeOptions.class.getName()));
assertEquals(6, filtered.size());
mapped = uniqueIndex(filtered, input -> input.getName());
PipelineOptionDescriptor listDesc = mapped.get("list");
assertThat(listDesc, notNullValue());
assertThat(listDesc.getDescription(), isEmptyString());
assertEquals(PipelineOptionType.Enum.ARRAY, listDesc.getType());
assertThat(listDesc.getDefaultValue(), isEmptyString());
PipelineOptionDescriptor stringDesc = mapped.get("string");
assertThat(stringDesc, notNullValue());
assertThat(stringDesc.getDescription(), isEmptyString());
assertEquals(PipelineOptionType.Enum.STRING, stringDesc.getType());
assertThat(stringDesc.getDefaultValue(), isEmptyString());
PipelineOptionDescriptor integerDesc = mapped.get("integer");
assertThat(integerDesc, notNullValue());
assertEquals("integer property", integerDesc.getDescription());
assertEquals(PipelineOptionType.Enum.INTEGER, integerDesc.getType());
assertThat(integerDesc.getDefaultValue(), isEmptyString());
PipelineOptionDescriptor floatDesc = mapped.get("float");
assertThat(integerDesc, notNullValue());
assertEquals("float number property", floatDesc.getDescription());
assertEquals(PipelineOptionType.Enum.NUMBER, floatDesc.getType());
assertThat(floatDesc.getDefaultValue(), isEmptyString());
PipelineOptionDescriptor booleanSimpleDesc = mapped.get("boolean_simple");
assertThat(booleanSimpleDesc, notNullValue());
assertEquals("simple boolean property", booleanSimpleDesc.getDescription());
assertEquals(PipelineOptionType.Enum.BOOLEAN, booleanSimpleDesc.getType());
assertThat(booleanSimpleDesc.getDefaultValue(), equalTo("true"));
PipelineOptionDescriptor booleanWrapperDesc = mapped.get("boolean_wrapper");
assertThat(booleanWrapperDesc, notNullValue());
assertThat(booleanWrapperDesc.getDescription(), isEmptyString());
assertEquals(PipelineOptionType.Enum.BOOLEAN, booleanWrapperDesc.getType());
assertThat(booleanWrapperDesc.getDefaultValue(), equalTo("false"));
}
}