// Copyright 2014 Google Inc. All rights reserved. // // 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 com.google.devtools.common.options; import static com.google.common.truth.Truth.assertThat; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.net.MalformedURLException; import java.net.URL; /** * Test for {@link Options}. */ @RunWith(JUnit4.class) public class OptionsTest { private static final String[] NO_ARGS = {}; public static class HttpOptions extends OptionsBase { @Option(name = "host", defaultValue = "www.google.com", help = "The URL at which the server will be running.") public String host; @Option(name = "port", abbrev = 'p', defaultValue = "80", help = "The port at which the server will be running.") public int port; @Option(name = "debug", abbrev = 'd', defaultValue = "false", help = "debug") public boolean isDebugging; @Option(name = "tristate", abbrev = 't', defaultValue = "auto", help = "tri-state option returning auto by default") public TriState triState; @Option(name = "special", defaultValue = "null", expansion = { "--host=special.google.com", "--port=8080"}) public Void special; } @Test public void paragraphFill() throws Exception { // TODO(bazel-team): don't include trailing space after last word in line. String input = "The quick brown fox jumps over the lazy dog."; assertEquals(" The quick \n brown fox \n jumps over \n the lazy \n" + " dog.", OptionsUsage.paragraphFill(input, 2, 13)); assertEquals(" The quick brown \n fox jumps over \n the lazy dog.", OptionsUsage.paragraphFill(input, 3, 19)); String input2 = "The quick brown fox jumps\nAnother paragraph."; assertEquals(" The quick brown fox \n jumps\n Another paragraph.", OptionsUsage.paragraphFill(input2, 2, 23)); } @Test public void getsDefaults() throws OptionsParsingException { Options options = Options.parse(HttpOptions.class, NO_ARGS); String[] remainingArgs = options.getRemainingArgs(); HttpOptions webFlags = options.getOptions(); assertEquals("www.google.com", webFlags.host); assertEquals(80, webFlags.port); assertEquals(false, webFlags.isDebugging); assertEquals(TriState.AUTO, webFlags.triState); assertEquals(0, remainingArgs.length); } @Test public void objectMethods() throws OptionsParsingException { String[] args = { "--host", "foo", "--port", "80" }; HttpOptions left = Options.parse(HttpOptions.class, args).getOptions(); HttpOptions likeLeft = Options.parse(HttpOptions.class, args).getOptions(); String [] rightArgs = {"--host", "other", "--port", "90" }; HttpOptions right = Options.parse(HttpOptions.class, rightArgs).getOptions(); String toString = left.toString(); // Don't rely on Set.toString iteration order: assertTrue(toString.startsWith( "com.google.devtools.common.options.OptionsTest" + "$HttpOptions{")); assertTrue(toString.contains("host=foo")); assertTrue(toString.contains("port=80")); assertTrue(toString.endsWith("}")); assertTrue(left.equals(left)); assertTrue(left.toString().equals(likeLeft.toString())); assertTrue(left.equals(likeLeft)); assertTrue(likeLeft.equals(left)); assertFalse(left.equals(right)); assertFalse(right.equals(left)); assertFalse(left.equals(null)); assertFalse(likeLeft.equals(null)); assertEquals(likeLeft.hashCode(), likeLeft.hashCode()); assertEquals(left.hashCode(), likeLeft.hashCode()); // Strictly speaking this is not required for hashCode to be correct, // but a good hashCode should be different at least for some values. So, // we're making sure that at least this particular pair of inputs yields // different values. assertFalse(left.hashCode() == right.hashCode()); } @Test public void equals() throws OptionsParsingException { String[] args = { "--host", "foo", "--port", "80" }; HttpOptions options1 = Options.parse(HttpOptions.class, args).getOptions(); String[] args2 = { "-p", "80", "--host", "foo" }; HttpOptions options2 = Options.parse(HttpOptions.class, args2).getOptions(); assertEquals("order/abbreviations shouldn't matter", options1, options2); assertEquals("explicitly setting a default shouldn't matter", Options.parse(HttpOptions.class, "--port", "80").getOptions(), Options.parse(HttpOptions.class).getOptions()); assertThat(Options.parse(HttpOptions.class, "--port", "3").getOptions()) .isNotEqualTo(Options.parse(HttpOptions.class).getOptions()); } @Test public void getsFlagsProvidedInArguments() throws OptionsParsingException { String[] args = {"--host", "google.com", "-p", "8080", // short form "--debug"}; Options options = Options.parse(HttpOptions.class, args); String[] remainingArgs = options.getRemainingArgs(); HttpOptions webFlags = options.getOptions(); assertEquals("google.com", webFlags.host); assertEquals(8080, webFlags.port); assertEquals(true, webFlags.isDebugging); assertEquals(0, remainingArgs.length); } @Test public void getsFlagsProvidedWithEquals() throws OptionsParsingException { String[] args = {"--host=google.com", "--port=8080", "--debug"}; Options options = Options.parse(HttpOptions.class, args); String[] remainingArgs = options.getRemainingArgs(); HttpOptions webFlags = options.getOptions(); assertEquals("google.com", webFlags.host); assertEquals(8080, webFlags.port); assertEquals(true, webFlags.isDebugging); assertEquals(0, remainingArgs.length); } @Test public void booleanNo() throws OptionsParsingException { Options options = Options.parse(HttpOptions.class, new String[]{"--nodebug", "--notristate"}); HttpOptions webFlags = options.getOptions(); assertEquals(false, webFlags.isDebugging); assertEquals(TriState.NO, webFlags.triState); } @Test public void booleanNoUnderscore() throws OptionsParsingException { Options options = Options.parse(HttpOptions.class, new String[]{"--no_debug", "--no_tristate"}); HttpOptions webFlags = options.getOptions(); assertEquals(false, webFlags.isDebugging); assertEquals(TriState.NO, webFlags.triState); } @Test public void booleanAbbrevMinus() throws OptionsParsingException { Options options = Options.parse(HttpOptions.class, new String[]{"-d-", "-t-"}); HttpOptions webFlags = options.getOptions(); assertEquals(false, webFlags.isDebugging); assertEquals(TriState.NO, webFlags.triState); } @Test public void boolean0() throws OptionsParsingException { Options options = Options.parse(HttpOptions.class, new String[]{"--debug=0", "--tristate=0"}); HttpOptions webFlags = options.getOptions(); assertEquals(false, webFlags.isDebugging); assertEquals(TriState.NO, webFlags.triState); } @Test public void boolean1() throws OptionsParsingException { Options options = Options.parse(HttpOptions.class, new String[]{"--debug=1", "--tristate=1"}); HttpOptions webFlags = options.getOptions(); assertEquals(true, webFlags.isDebugging); assertEquals(TriState.YES, webFlags.triState); } @Test public void retainsStuffThatsNotOptions() throws OptionsParsingException { String[] args = {"these", "aint", "options"}; Options options = Options.parse(HttpOptions.class, args); String[] remainingArgs = options.getRemainingArgs(); assertEquals(asList(args), asList(remainingArgs)); } @Test public void retainsStuffThatsNotComplexOptions() throws OptionsParsingException { String[] args = {"--host", "google.com", "notta", "--port=8080", "option", "--debug=true"}; String[] notoptions = {"notta", "option" }; Options options = Options.parse(HttpOptions.class, args); String[] remainingArgs = options.getRemainingArgs(); assertEquals(asList(notoptions), asList(remainingArgs)); } @Test public void wontParseUnknownOptions() { String [] args = { "--unknown", "--other=23", "--options" }; try { Options.parse(HttpOptions.class, args); fail(); } catch (OptionsParsingException e) { assertEquals("Unrecognized option: --unknown", e.getMessage()); } } @Test public void requiresOptionValue() { String[] args = {"--port"}; try { Options.parse(HttpOptions.class, args); fail(); } catch (OptionsParsingException e) { assertEquals("Expected value after --port", e.getMessage()); } } @Test public void handlesDuplicateOptions_full() throws Exception { String[] args = {"--port=80", "--port", "81"}; Options options = Options.parse(HttpOptions.class, args); HttpOptions webFlags = options.getOptions(); assertEquals(81, webFlags.port); } @Test public void handlesDuplicateOptions_abbrev() throws Exception { String[] args = {"--port=80", "-p", "81"}; Options options = Options.parse(HttpOptions.class, args); HttpOptions webFlags = options.getOptions(); assertEquals(81, webFlags.port); } @Test public void duplicateOptionsOkWithSameValues() throws Exception { // These would throw OptionsParsingException if they failed. Options.parse(HttpOptions.class,"--port=80", "--port", "80"); Options.parse(HttpOptions.class, "--port=80", "-p", "80"); } @Test public void isPickyAboutBooleanValues() { try { Options.parse(HttpOptions.class, new String[]{"--debug=not_a_boolean"}); fail(); } catch (OptionsParsingException e) { assertEquals("While parsing option --debug=not_a_boolean: " + "\'not_a_boolean\' is not a boolean", e.getMessage()); } } @Test public void isPickyAboutBooleanNos() { try { Options.parse(HttpOptions.class, new String[]{"--nodebug=1"}); fail(); } catch (OptionsParsingException e) { assertEquals("Unexpected value after boolean option: --nodebug=1", e.getMessage()); } } @Test public void usageForBuiltinTypes() { String usage = Options.getUsage(HttpOptions.class); // We can't rely on the option ordering. assertTrue(usage.contains( " --[no]debug [-d] (a boolean; default: \"false\")\n" + " debug")); assertTrue(usage.contains( " --host (a string; default: \"www.google.com\")\n" + " The URL at which the server will be running.")); assertTrue(usage.contains( " --port [-p] (an integer; default: \"80\")\n" + " The port at which the server will be running.")); assertTrue(usage.contains( " --special\n" + " Expands to: --host=special.google.com --port=8080")); assertTrue(usage.contains( " --[no]tristate [-t] (a tri-state (auto, yes, no); default: \"auto\")\n" + " tri-state option returning auto by default")); } public static class NullTestOptions extends OptionsBase { @Option(name = "host", defaultValue = "null", help = "The URL at which the server will be running.") public String host; @Option(name = "none", defaultValue = "null", expansion = {"--host=www.google.com"}, help = "An expanded option.") public Void none; } @Test public void usageForNullDefault() { String usage = Options.getUsage(NullTestOptions.class); assertTrue(usage.contains( " --host (a string; default: see description)\n" + " The URL at which the server will be running.")); assertTrue(usage.contains( " --none\n" + " An expanded option.\n" + " Expands to: --host=www.google.com")); } public static class MyURLConverter implements Converter { @Override public URL convert(String input) throws OptionsParsingException { try { return new URL(input); } catch (MalformedURLException e) { throw new OptionsParsingException("Could not convert '" + input + "': " + e.getMessage()); } } @Override public String getTypeDescription() { return "a url"; } } public static class UsesCustomConverter extends OptionsBase { @Option(name = "url", defaultValue = "http://www.google.com/", converter = MyURLConverter.class) public URL url; } @Test public void customConverter() throws Exception { Options options = Options.parse(UsesCustomConverter.class, new String[0]); URL expected = new URL("http://www.google.com/"); assertEquals(expected, options.getOptions().url); } @Test public void customConverterThrowsException() throws Exception { String[] args = {"--url=a_malformed:url"}; try { Options.parse(UsesCustomConverter.class, args); fail(); } catch (OptionsParsingException e) { assertEquals("While parsing option --url=a_malformed:url: " + "Could not convert 'a_malformed:url': " + "no protocol: a_malformed:url", e.getMessage()); } } @Test public void usageWithCustomConverter() { assertEquals( " --url (a url; default: \"http://www.google.com/\")\n", Options.getUsage(UsesCustomConverter.class)); } @Test public void unknownBooleanOption() { try { Options.parse(HttpOptions.class, new String[]{"--no-debug"}); fail(); } catch (OptionsParsingException e) { assertEquals("Unrecognized option: --no-debug", e.getMessage()); } } public static class J extends OptionsBase { @Option(name = "j", defaultValue = "null") public String string; } @Test public void nullDefaultForReferenceTypeOption() throws Exception { J options = Options.parse(J.class, NO_ARGS).getOptions(); assertNull(options.string); } public static class K extends OptionsBase { @Option(name = "1", defaultValue = "null") public int int1; } @Test public void nullDefaultForPrimitiveTypeOption() throws Exception { // defaultValue() = "null" is not treated specially for primitive types, so // we get an NumberFormatException from the converter (not a // ClassCastException from casting null to int), just as we would for any // other non-integer-literal string default. try { Options.parse(K.class, NO_ARGS).getOptions(); fail(); } catch (IllegalStateException e) { assertEquals("OptionsParsingException while retrieving default for " + "int1: 'null' is not an int", e.getMessage()); } } @Test public void nullIsntInterpretedSpeciallyExceptAsADefaultValue() throws Exception { HttpOptions options = Options.parse(HttpOptions.class, new String[] { "--host", "null" }).getOptions(); assertEquals("null", options.host); } @Test public void nonDecimalRadicesForIntegerOptions() throws Exception { Options options = Options.parse(HttpOptions.class, new String[] { "--port", "0x51"}); assertEquals(81, options.getOptions().port); } @Test public void expansionOptionSimple() throws Exception { Options options = Options.parse(HttpOptions.class, new String[] {"--special"}); assertEquals("special.google.com", options.getOptions().host); assertEquals(8080, options.getOptions().port); } @Test public void expansionOptionOverride() throws Exception { Options options = Options.parse(HttpOptions.class, new String[] {"--port=90", "--special", "--host=foo"}); assertEquals("foo", options.getOptions().host); assertEquals(8080, options.getOptions().port); } @Test public void expansionOptionEquals() throws Exception { Options options1 = Options.parse(HttpOptions.class, new String[] { "--host=special.google.com", "--port=8080"}); Options options2 = Options.parse(HttpOptions.class, new String[] { "--special" }); assertEquals(options1.getOptions(), options2.getOptions()); } }