/* Copyright 2013 Jonatan Jönsson * * Licensed 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 se.softhouse.jargo.defaultvalues; import static java.lang.String.format; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static org.fest.assertions.Assertions.assertThat; import static org.fest.assertions.Fail.fail; import static se.softhouse.jargo.Arguments.booleanArgument; import static se.softhouse.jargo.Arguments.fileArgument; import static se.softhouse.jargo.Arguments.integerArgument; import static se.softhouse.jargo.Arguments.stringArgument; import static se.softhouse.jargo.Arguments.withParser; import static se.softhouse.jargo.StringParsers.stringParser; import static se.softhouse.jargo.limiters.FooLimiter.foos; import static se.softhouse.jargo.stringparsers.custom.ObjectParser.objectArgument; import static se.softhouse.jargo.utils.Assertions2.assertThat; import static se.softhouse.jargo.utils.ExpectedTexts.expected; import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Map; import org.junit.Test; import se.softhouse.common.strings.Describer; import se.softhouse.common.testlib.Explanation; import se.softhouse.jargo.Argument; import se.softhouse.jargo.ArgumentBuilder; import se.softhouse.jargo.ArgumentBuilder.DefaultArgumentBuilder; import se.softhouse.jargo.ArgumentException; import se.softhouse.jargo.ForwardingStringParser; import se.softhouse.jargo.StringParser; import se.softhouse.jargo.Usage; import se.softhouse.jargo.internal.Texts.ProgrammaticErrors; import se.softhouse.jargo.internal.Texts.UserErrors; import se.softhouse.jargo.stringparsers.custom.NullReturningParser; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Range; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * Tests for {@link StringParser#defaultValue()}, {@link ArgumentBuilder#defaultValue(Object)} and * {@link ArgumentBuilder#defaultValueSupplier(Supplier)} */ public class DefaultValueTest { @Test public void testThatNonRequiredAndNonDefaultedArgumentDefaultsToZero() throws ArgumentException { assertThat(integerArgument("-n").parse()).isZero(); } @Test public void testThatNonRequiredAndNonDefaultedRepeatedArgumentDefaultsToEmptyList() throws ArgumentException { List<Integer> numbers = integerArgument("-n").repeated().parse(); assertThat(numbers).isEmpty(); } @Test(expected = IllegalStateException.class) public void testThatInvalidDefaultValueFromStringParserIsInvalidated() throws ArgumentException { withParser(new ForwardingStringParser.SimpleForwardingStringParser<String>(stringParser()){ @Override public String defaultValue() { return "bar"; } }).limitTo(foos()).parse(); } @Test public void testThatInvalidDefaultValueSupplierValuesAreInvalidated() throws ArgumentException { try { // Throws because bar (which is given by BarSupplier) isn't foo stringArgument("-n").defaultValueSupplier(new BarSupplier()).limitTo(foos()).parse(); fail("only foo should be allowed, not bar"); } catch(IllegalStateException e) { assertThat(e).hasMessage(format(ProgrammaticErrors.INVALID_DEFAULT_VALUE, format(UserErrors.DISALLOWED_VALUE, "bar", "foo"))); } } @Test public void testThatDefaultValueSupplierIsNotUsedWhenArgumentIsGiven() throws ArgumentException { ProfilingSupplier profiler = new ProfilingSupplier(); int one = integerArgument().defaultValueSupplier(profiler).limitTo(Range.closed(0, 10)).parse("1"); assertThat(one).isEqualTo(1); assertThat(profiler.callsToGet).isZero(); } @Test @SuppressFBWarnings(value = "RV_RETURN_VALUE_IGNORED", justification = Explanation.FAIL_FAST) public void testThatInvalidDefaultValueInRepeatedArgumentIsInvalidatedDuringBuild() { try { // Throws because bar (which is given by BarSupplier) isn't foo stringArgument("-n").defaultValue("bar").limitTo(foos()).repeated().build(); fail("only foo should be allowed, not bar"); } catch(IllegalStateException e) { assertThat(e).hasMessage(format(ProgrammaticErrors.INVALID_DEFAULT_VALUE, format(UserErrors.DISALLOWED_VALUE, "bar", "foo"))); assertThat(e.getCause()).isInstanceOf(IllegalArgumentException.class); } } @Test(expected = UnsupportedOperationException.class) public void testThatDefaultValuesProvidedToRepeatedArgumentsAreImmutable() throws ArgumentException { // Should throw because defaultValue makes its argument Immutable integerArgument("-n").repeated().defaultValue(asList(1, 2)).parse().add(3); } @Test(expected = UnsupportedOperationException.class) public void testThatDefaultValuesProvidedToListArgumentsAreImmutable() throws ArgumentException { // Should throw because defaultValue makes its argument Immutable integerArgument("-n").arity(2).defaultValue(asList(1, 2)).parse().add(3); } @Test(expected = UnsupportedOperationException.class) public void testThatDefaultValuesProvidedToSplitArgumentsAreImmutable() throws ArgumentException { // Should throw because defaultValue makes its argument Immutable integerArgument("-n").splitWith(",").defaultValue(asList(1, 2)).parse().add(3); } @Test(expected = RuntimeException.class) @SuppressFBWarnings(value = "RV_RETURN_VALUE_IGNORED", justification = Explanation.FAIL_FAST) public void testThatRequiredArgumentsCantHaveADefaultValueSupplier() { stringArgument("-n").required().defaultValueSupplier(new BarSupplier()); } @Test public void testThatADefaultValueSupplierIsMovedBetweenBuilders() throws ArgumentException { DefaultArgumentBuilder<String> builder = stringArgument("-n").defaultValueSupplier(new BarSupplier()); Argument<List<String>> argument = builder.repeated().build(); testUnmodifiableDefaultList(argument); argument = builder.splitWith(",").build(); testUnmodifiableDefaultList(argument); } private void testUnmodifiableDefaultList(Argument<List<String>> argument) throws ArgumentException { List<String> defaultValue = argument.parse(); assertThat(defaultValue).isEqualTo(Arrays.asList("bar")); try { defaultValue.add("foo"); fail("Lists with default values in them should be unmodifiable"); } catch(UnsupportedOperationException expected) { } } @Test public void testNullAsDefaultValue() throws ArgumentException { assertThat(integerArgument("-n").defaultValue(null).parse()).isNull(); } @Test public void testThatDefaultValueIsNotUsedWhenParserReturnsNull() throws ArgumentException { Object actualValue = withParser(new NullReturningParser()).defaultValue("defaultValue").parse("bar"); assertThat(actualValue).isNull(); } @Test public void testThatDefaultValueProviderIsAskedForEachArgumentParsing() throws ArgumentException { Supplier<Integer> supplier = new ChangingSupplier(); Argument<Integer> n = integerArgument("-n").defaultValueSupplier(supplier).build(); assertThat(n.parse()).isNotEqualTo(n.parse()); } @Test public void testThatDefaultValueIsUsedForEachValueInArityArgument() throws ArgumentException { assertThat(integerArgument("-n").defaultValue(1).arity(2).parse()).isEqualTo(asList(1, 1)); } @Test public void testThatNullDefaultValueIsDescribedAsNull() { assertThat(stringArgument("-n").arity(2).defaultValue(null).usage()).contains("null"); assertThat(integerArgument("-n").arity(2).defaultValue(null).usage()).contains("null"); assertThat(integerArgument("-n").defaultValue(null).usage()).contains("null"); assertThat(fileArgument("-n").defaultValue(null).usage()).contains("null"); } @Test public void testDefaultValuesForMultipleParametersForNamedArgument() throws ArgumentException { List<Integer> defaults = asList(5, 6); List<Integer> numbers = integerArgument("--numbers").variableArity().defaultValue(defaults).parse(); assertThat(numbers).isEqualTo(defaults); } @Test public void testVariableArityDefaultsToOneElementListIfDefaultValueIsPreviouslySet() throws ArgumentException { List<Integer> numbers = integerArgument("--numbers").defaultValue(42).variableArity().parse(); assertThat(numbers).isEqualTo(asList(42)); } @Test public void testVariableArityDefaultsToZeroElementsIfDefaultValueIsNotPreviouslySet() throws ArgumentException { List<Integer> numbers = integerArgument("--numbers").variableArity().parse(); assertThat(numbers).isEqualTo(emptyList()); } @Test(expected = UnsupportedOperationException.class) public void testThatDefaultValueIsImmutableWhenUsedForEachValueInArityArgument() throws ArgumentException { integerArgument("-n").defaultValue(1).arity(2).parse().add(3); } @Test public void testThatEachDefaultValueIsDescribedInArityArgument() { Usage usage = integerArgument("-n").defaultValue(1).defaultValueDescription("One").arity(2).usage(); assertThat(usage).isEqualTo(expected("arityOfDefaultValuesDescribed")); } @Test public void testThatDefaultValueIsDescribedWithEmptyListForARepeatableListEvenThoughADefaultValueDescriberHasBeenSet() { // When a defaultValueDescription has been set before repeated/arity/variableArity that // description is used for each value but if there is no value there is nothing to describe Usage usage = integerArgument("-n").defaultValueDescription("SomethingThatWillBeReplacedWithEmptyList").repeated().usage(); assertThat(usage).contains("Default: Empty list [Supports Multiple occurrences]"); } @Test public void testThatDefaultValuesCanBeDescribedWithObjectDescriber() { // If a describer can describe an Object, it can also describe an Integer Describer<Object> hashCodeDescriber = new Describer<Object>(){ @Override public String describe(Object value, Locale inLocale) { return "(Hashcode) " + value.hashCode(); } }; Usage usage = integerArgument("-n").defaultValueDescriber(hashCodeDescriber).usage(); assertThat(usage).contains("Default: (Hashcode) 0"); } @Test public void testThatDefaultValuesCanBeSuppliedAsIntegersForObjectArgument() { Supplier<Integer> integerSupply = new Supplier<Integer>(){ @Override public Integer get() { return 2; } }; Usage usage = objectArgument().defaultValueSupplier(integerSupply).usage(); assertThat(usage).contains("Default: 2"); } @Test public void testThatDefaultValueIsUsedForNonExistingKeys() { Map<String, Boolean> parsedKeys = booleanArgument("-n").defaultValue(true) // .asPropertyMap().parse(); Boolean defaultValue = parsedKeys.get("non-existing-key"); assertThat(defaultValue).as("setting defaultValue on argument should mean defaultValue for values in a key/value map").isTrue(); } @Test public void testThatExistingKeysDoNotUseDefaultValue() { Map<String, Boolean> parsedKeys = booleanArgument("-n").defaultValue(true) // .asPropertyMap().parse("-nexisting-key=false"); Boolean givenValue = parsedKeys.get("existing-key"); assertThat(givenValue).isFalse(); Boolean defaultValue = parsedKeys.get("non-existing-key"); assertThat(defaultValue).isTrue(); } @Test public void testThatDefaultValueDescriberForValuesInAKeyValueArgumentIsUsedForValues() { Map<String, Integer> defaults = ImmutableMap.<String, Integer>builder().put("foo", 4000).build(); Usage usage = integerArgument("-n").asPropertyMap().defaultValue(defaults).usage(); assertThat(usage).isEqualTo(expected("defaultValuesInPropertyMapDescribedByDescriber")); } @Test public void testThatMapDescriberIsUsedAsDefaultValueDescriberForValuesInAKeyValueArgument() { Usage usage = stringArgument("-n").asPropertyMap().usage(); assertThat(usage).contains("Empty map"); } }