/*
* 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.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.not;
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.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.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 com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ListMultimap;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.RestoreSystemProperties;
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.getRegisteredRunners();
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 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);
}
private interface MultiGetters extends PipelineOptions {
Object getObject();
void setObject(Object value);
@JsonIgnore
Integer getOther();
void setOther(Integer value);
Void getConsistent();
void setConsistent(Void consistent);
}
private 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}.
*/
private 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}.
*/
private 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}.
*/
private 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.
*/
private 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);
}
private 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);
}
private 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);
}
private 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(Float.valueOf(55.5f), options.getFloat(), 0.0f);
assertEquals(Double.valueOf(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. */
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");
}
/** 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.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();
}
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());
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());
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());
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());
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());
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. */
private static class NameConflictClassA {
/** Used for a name collision test with the other NameConflict interfaces. */
private interface NameConflict extends PipelineOptions {
}
}
/** Used for a name collision test with the other NameConflict interfaces. */
private static class NameConflictClassB {
/** Used for a name collision test with the other NameConflict interfaces. */
private 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());
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());
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());
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());
// 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());
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());
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."));
}
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.<Class<? extends PipelineRunner<?>>>of(RegisteredTestRunner.class);
}
}
private 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.<Class<? extends PipelineOptions>>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. */
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);
}
}
}