/* 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;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Maps.newLinkedHashMap;
import static java.lang.String.format;
import static java.util.Arrays.asList;
import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.Fail.fail;
import static se.softhouse.common.strings.Describers.withConstantString;
import static se.softhouse.jargo.Arguments.byteArgument;
import static se.softhouse.jargo.Arguments.integerArgument;
import static se.softhouse.jargo.Arguments.stringArgument;
import static se.softhouse.jargo.StringParsers.byteParser;
import static se.softhouse.jargo.StringParsers.integerParser;
import static se.softhouse.jargo.internal.Texts.UserErrors.DISALLOWED_PROPERTY_VALUE;
import static se.softhouse.jargo.limiters.FooLimiter.foos;
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.Map;
import org.junit.Test;
import se.softhouse.common.testlib.Explanation;
import se.softhouse.jargo.internal.Texts.ProgrammaticErrors;
import se.softhouse.jargo.internal.Texts.UserErrors;
import se.softhouse.jargo.stringparsers.custom.LimitedKeyParser;
import se.softhouse.jargo.stringparsers.custom.ObjectParser;
import com.google.common.base.Predicate;
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 ArgumentBuilder#asPropertyMap()} and
* {@link ArgumentBuilder#asKeyValuesWithKeyParser(StringParser)}
*/
public class PropertyMapTest
{
@Test
public void testSeveralValues() throws ArgumentException
{
Map<String, Integer> numberMap = integerArgument("-N").asPropertyMap().parse("-None=1", "-Ntwo=2");
assertThat(numberMap.get("one")).isEqualTo(1);
assertThat(numberMap.get("two")).isEqualTo(2);
}
@Test
public void testStartsWithCollisionsForTwoSimiliarIdentifiers() throws ArgumentException
{
Map<String, Integer> numberMap = integerArgument("-NS", "-N").asPropertyMap().parse("-None=1", "-NStwo=2");
assertThat(numberMap.get("one")).isEqualTo(1);
assertThat(numberMap.get("two")).isEqualTo(2);
numberMap = integerArgument("-N", "-NS").asPropertyMap().parse("-None=1", "-NStwo=2");
assertThat(numberMap.get("one")).isEqualTo(1);
assertThat(numberMap.get("two")).isEqualTo(2);
numberMap = integerArgument("-N", "-D").asPropertyMap().parse("-Done=1", "-Ntwo=2");
assertThat(numberMap.get("one")).isEqualTo(1);
assertThat(numberMap.get("two")).isEqualTo(2);
}
@Test
public void testNameCollisionWithOrdinaryArgument() throws ArgumentException
{
Argument<Map<String, Integer>> numberMap = integerArgument("-N").asPropertyMap().build();
Argument<Integer> number = integerArgument("-N").ignoreCase().build();
ParsedArguments parsed = CommandLineParser.withArguments(numberMap, number).parse("-None=1", "-Ntwo=2", "-N", "3");
assertThat(parsed.get(numberMap).get("one")).isEqualTo(1);
assertThat(parsed.get(numberMap).get("two")).isEqualTo(2);
assertThat(parsed.get(number)).isEqualTo(3);
}
@Test
@SuppressFBWarnings(value = "RV_RETURN_VALUE_IGNORED", justification = Explanation.FAIL_FAST)
public void testPropertyMapWithoutLeadingIdentifier()
{
try
{
integerArgument().asPropertyMap().build();
fail("a property map must be prefixed with an identifier otherwise it would consume all arguments");
}
catch(IllegalStateException expected)
{
assertThat(expected).hasMessage(ProgrammaticErrors.NO_NAME_FOR_PROPERTY_MAP);
}
}
@Test
public void testInvalidationOfWrongSeparator()
{
try
{
integerArgument("-N").asPropertyMap().parse("-N3");
fail("-N3 should be missing a =");
}
catch(ArgumentException expected)
{
assertThat(expected).hasMessage(String.format(UserErrors.MISSING_KEY_VALUE_SEPARATOR, "-N", "3", "="));
}
}
@Test
public void testCustomSeparator() throws ArgumentException
{
Map<String, Integer> numberMap = integerArgument("-N").separator("/").asPropertyMap().parse("-Nkey/3");
assertThat(numberMap.get("key")).isEqualTo(3);
}
@Test
public void testLimitationOfPropertyValues() throws ArgumentException
{
Argument<Map<String, String>> fooArgument = stringArgument("-N").limitTo(foos()).asPropertyMap().build();
CommandLineParser parser = CommandLineParser.withArguments(fooArgument);
ParsedArguments parsed = parser.parse("-Nbar=foo");
assertThat(parsed.get(fooArgument).get("bar")).isEqualTo("foo");
try
{
parser.parse("-Nbar=bar");
fail("bar should not be a valid value");
}
catch(ArgumentException expected)
{
assertThat(expected).hasMessage(format(DISALLOWED_PROPERTY_VALUE, "bar", "bar", "foo"));
}
}
@Test(expected = ArgumentException.class)
public void testLimitationOfPropertyMapKeys() throws ArgumentException
{
integerArgument("-I").asKeyValuesWithKeyParser(new LimitedKeyParser("foo", "bar")).parse("-Ifoo=10", "-Ibar=5", "-Izoo=9");
}
@Test
public void testLimitationOfPropertyMapKeysAndValues()
{
Predicate<Integer> zeroToTen = Range.closed(0, 10);
Argument<Map<String, Integer>> argument = integerArgument("-I").limitTo(zeroToTen)
.asKeyValuesWithKeyParser(new LimitedKeyParser("foo", "bar")).build();
CommandLineParser parser = CommandLineParser.withArguments(argument);
try
{
parser.parse("-Ifoo=1", "-Ibar=2", "-Izoo=3");
fail("Didn't invalidate zoo key");
}
catch(ArgumentException expected)
{
Usage usage = expected.getMessageAndUsage();
assertThat(usage).isEqualTo(expected("limiterUsageForBothValueLimiterAndKeyLimiter"));
}
try
{
parser.parse("-Ifoo=1", "-Ibar=-1");
fail("Didn't invalidate bar with negative value");
}
catch(ArgumentException invalidBar)
{
assertThat(invalidBar).hasMessage(format(DISALLOWED_PROPERTY_VALUE, "bar", -1, zeroToTen));
}
}
@Test
public void testThatPropertyValuesDefaultToAnUnmodifiableEmptyMap() throws ArgumentException
{
Map<String, Integer> defaultMap = integerArgument("-N").asPropertyMap().parse();
assertThat(defaultMap).isEmpty();
try
{
defaultMap.put("a", 42);
fail("the defaultMap should be unmodifiable");
}
catch(UnsupportedOperationException expected)
{
}
}
@Test
public void testCustomKeyParser() throws ArgumentException
{
assertThat(integerArgument("-N").asKeyValuesWithKeyParser(integerParser()).parse("-N1=42").get(1)).isEqualTo(42);
}
@Test
public void testThatPropertyMapsAreUnmodifiable() throws ArgumentException
{
Map<String, Integer> numberMap = integerArgument("-N").asPropertyMap().parse("-None=1");
try
{
numberMap.put("two", 2);
fail("a propertyMap should be unmodifiable");
}
catch(UnsupportedOperationException expected)
{
}
}
@Test
public void testThatPropertyMapsWithRepeatedValuesAreUnmodifiable() throws ArgumentException
{
Map<String, List<Integer>> numberMap = integerArgument("-N").repeated().asPropertyMap().parse("-Nfoo=1", "-Nfoo=2");
try
{
numberMap.put("bar", Arrays.asList(1, 2));
fail("a propertyMap should be unmodifiable");
}
catch(UnsupportedOperationException expected)
{
}
try
{
numberMap.get("foo").add(3);
fail("a list inside a propertyMap should be unmodifiable");
}
catch(UnsupportedOperationException expected)
{
}
}
@SuppressWarnings("deprecation")
@Test(expected = IllegalStateException.class)
public void testThatRepeatedMustBeCalledBeforeAsPropertyMap()
{
integerArgument("-N").asPropertyMap().repeated();
}
@SuppressWarnings("deprecation")
@Test(expected = IllegalStateException.class)
public void testThatArityMustBeCalledBeforeAsPropertyMap()
{
integerArgument("-N").asPropertyMap().arity(2);
}
@SuppressWarnings("deprecation")
@Test(expected = IllegalStateException.class)
public void testThatVariableArityAndAsPropertyMapIsIncompatible()
{
integerArgument("-N").asPropertyMap().variableArity();
}
@Test
public void testThatIterationOrderForPropertyKeysIsTheSameAsFromTheCommandLine() throws ArgumentException
{
Map<String, Integer> map = integerArgument("-I").asPropertyMap().parse("-Itldr=7", "-Ifoo=10", "-Ibar=5");
List<String> keys = newArrayList();
for(String key : map.keySet())
{
keys.add(key);
}
assertThat(keys).isEqualTo(asList("tldr", "foo", "bar"));
}
@Test
public void testDefaultValuesInUsageForPropertyMap()
{
Map<Byte, Byte> defaults = newLinkedHashMap();
defaults.put((byte) 3, (byte) 4);
defaults.put((byte) 1, (byte) 2);
Usage usage = byteArgument("-N").asKeyValuesWithKeyParser(byteParser()).separator(":").defaultValue(defaults).usage();
assertThat(usage).isEqualTo(expected("defaultValuePropertyMap"));
}
@Test
public void testThatSeparatorsWithSeveralCharactersArePossible() throws ArgumentException
{
String value = stringArgument("-N").asPropertyMap().separator("==").parse("-Nkey==value").get("key");
assertThat(value).isEqualTo("value");
}
@Test
@SuppressFBWarnings(value = "RV_RETURN_VALUE_IGNORED", justification = Explanation.FAIL_FAST)
public void testThatZeroCharacterSeparatorIsForbidden()
{
try
{
stringArgument("-N").separator("").asPropertyMap().build();
fail("an empty separator must be forbidden, otherwise it would be impossible to distinguish where the key ends and the value starts");
}
catch(IllegalStateException expected)
{
assertThat(expected).hasMessage(ProgrammaticErrors.EMPTY_SEPARATOR);
}
}
@Test
public void testThatUsageTextForRepeatedPropertyValuesLooksGood()
{
Usage usage = integerArgument("-N").repeated().asPropertyMap().description("Some measurement values").usage();
assertThat(usage).isEqualTo(expected("repeatedPropertyValues"));
}
@Test
public void testSeparatorInName() throws ArgumentException
{
Integer ten = integerArgument("-N;").separator(";").asPropertyMap().parse("-N;foo;10").get("foo");
assertThat(ten).isEqualTo(10);
}
@Test
public void testLimitWithForRepeatedPropertyValues()
{
try
{
stringArgument("-N").limitTo(foos()).repeated().asPropertyMap().parse("-Nkey=foo", "-Nkey=bar");
}
catch(ArgumentException expected)
{
assertThat(expected).hasMessage("'bar' is not foo");
}
}
@Test
public void testArityAndPropertyMap() throws ArgumentException
{
// A bit crazy but it is supported
Map<String, List<Integer>> value = integerArgument("-D").arity(2).asPropertyMap().parse("-Dt=1", "2");
assertThat(value.get("t")).isEqualTo(Arrays.asList(1, 2));
}
@Test
public void testDefaultValueForOnePropertyAndCommandLineValueForOneProperty() throws ArgumentException
{
Map<String, Integer> defaults = ImmutableMap.<String, Integer>builder().put("n", 10).build();
Map<String, Integer> values = integerArgument("-D").asPropertyMap().defaultValue(defaults).parse("-Ds=20");
assertThat(values.get("s")).isEqualTo(20);
assertThat(values.get("n")).isEqualTo(10);
}
@Test
public void testOverridingDefaultValue() throws ArgumentException
{
Map<String, Integer> defaults = ImmutableMap.<String, Integer>builder().put("n", 1).build();
int n = integerArgument("-D").asPropertyMap().defaultValue(defaults).parse("-Dn=2").get("n");
assertThat(n).isEqualTo(2);
}
@Test
public void testThatCustomDescriberCanBeUsedForPropertyMaps()
{
Usage usage = integerArgument("-D").asPropertyMap().defaultValueDescriber(withConstantString("a map")).usage();
assertThat(usage).contains("Default: a map");
}
@Test
public void testThatSeparatorIsNotInSuggestions() throws Exception
{
try
{
integerArgument("-n").asPropertyMap().parse("-a");
fail("-a should be suggested to -n");
}
catch(ArgumentException expected)
{
assertThat(expected).hasMessage(String.format(UserErrors.SUGGESTION, "-a", "-n"));
}
}
@Test
public void testThatSystemPropertiesCanBeUsedAsTargetMap() throws Exception
{
Map<Object, Object> map = Arguments.withParser(new ObjectParser()).names("-D").asKeyValuesWithKeyParser(new ObjectParser())
.defaultValueSupplier(new Supplier<Map<Object, Object>>(){
@Override
public Map<Object, Object> get()
{
return System.getProperties();
}
}).parse("-Dsys.prop.test=foo");
assertThat(map.get("sys.prop.test")).isEqualTo("foo");
assertThat(System.getProperty("sys.prop.test")).isEqualTo("foo");
assertThat(map.get("os.name")).as("Should delegate to system properties when not specified") //
.isEqualTo(System.getProperty("os.name"));
}
}