blob: 16cfeff1ff0da455f81f3b1c0b57aaceb4fbf6cd [file] [log] [blame]
// Copyright 2017 The Bazel Authors. 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 org.junit.Assert.assertThrows;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import com.google.devtools.common.options.Converters.AssignmentConverter;
import com.google.devtools.common.options.Converters.IntegerConverter;
import com.google.devtools.common.options.Converters.StringConverter;
import com.google.devtools.common.options.OptionDefinition.NotAnOptionException;
import com.google.devtools.common.options.OptionsParser.ConstructionException;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mockito;
/** Tests for {@link OptionDefinition}. */
@RunWith(JUnit4.class)
public class OptionDefinitionTest {
/** Dummy options class, to test various expected failures of the OptionDefinition. */
public static class BrokenOptions extends OptionsBase {
public String notAnOption;
@Option(
name = "assignments",
defaultValue = "foo is not an assignment",
converter = AssignmentConverter.class,
documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
effectTags = OptionEffectTag.NO_OP
)
public Map.Entry<String, String> assignments;
}
@Test
public void optionConverterCannotParseDefaultValue() throws Exception {
OptionDefinition optionDef =
OptionDefinition.extractOptionDefinition(BrokenOptions.class.getField("assignments"));
ConstructionException e =
assertThrows(
"Incorrect default should have caused getDefaultValue to fail.",
ConstructionException.class,
() -> optionDef.getDefaultValue());
assertThat(e)
.hasMessageThat()
.contains(
"OptionsParsingException while retrieving the default value for assignments: "
+ "Variable definitions must be in the form of a 'name=value' assignment");
}
@Test
public void optionDefinitionRejectsNonOptions() throws Exception {
NotAnOptionException e =
assertThrows(
"notAnOption isn't an Option, and shouldn't be accepted as one.",
NotAnOptionException.class,
() ->
OptionDefinition.extractOptionDefinition(
BrokenOptions.class.getField("notAnOption")));
assertThat(e)
.hasMessageThat()
.contains(
"The field notAnOption does not have the right annotation to be considered an "
+ "option.");
}
/**
* Dummy options class with valid options for testing the memoization of converters and default
* values.
*/
public static class ValidOptionUsingDefaultConverterForMocking extends OptionsBase {
@Option(
name = "foo",
documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
effectTags = {OptionEffectTag.NO_OP},
defaultValue = "42"
)
public int foo;
@Option(
name = "bar",
converter = StringConverter.class,
documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
effectTags = {OptionEffectTag.NO_OP},
defaultValue = "strings"
)
public String bar;
}
/**
* Test that the converter and option default values are only computed once and then are obtained
* from the stored values, in the case where a default converter is used.
*/
@Test
public void optionDefinitionMemoizesDefaultConverterValue() throws Exception {
OptionDefinition optionDefinition =
OptionDefinition.extractOptionDefinition(
ValidOptionUsingDefaultConverterForMocking.class.getField("foo"));
OptionDefinition mockOptionDef = Mockito.spy(optionDefinition);
// Do a bunch of potentially repeat operations on this option that need to know information
// about the converter and default value. Also verify that the values are as expected.
boolean isBoolean = mockOptionDef.usesBooleanValueSyntax();
assertThat(isBoolean).isFalse();
Converter<?> converter = mockOptionDef.getConverter();
assertThat(converter).isInstanceOf(IntegerConverter.class);
int value = (int) mockOptionDef.getDefaultValue();
assertThat(value).isEqualTo(42);
// Expect reference equality, since we didn't recompute the value
Converter<?> secondConverter = mockOptionDef.getConverter();
assertThat(secondConverter).isSameInstanceAs(converter);
mockOptionDef.getDefaultValue();
// Verify that we didn't re-calculate the converter from the provided class object.
verify(mockOptionDef, times(1)).getProvidedConverter();
// The first call to getDefaultValue checks isSpecialNullDefault, which called
// getUnparsedValueDefault as well, but expect no more calls to it after the initial call.
verify(mockOptionDef, times(1)).isSpecialNullDefault();
verify(mockOptionDef, times(2)).getUnparsedDefaultValue();
}
/**
* Test that the converter and option default values are only computed once and then are obtained
* from the stored values, in the case where a converter was provided.
*/
@Test
public void optionDefinitionMemoizesProvidedConverterValue() throws Exception {
OptionDefinition optionDefinition =
OptionDefinition.extractOptionDefinition(
ValidOptionUsingDefaultConverterForMocking.class.getField("bar"));
OptionDefinition mockOptionDef = Mockito.spy(optionDefinition);
// Do a bunch of potentially repeat operations on this option that need to know information
// about the converter and default value. Also verify that the values are as expected.
boolean isBoolean = mockOptionDef.usesBooleanValueSyntax();
assertThat(isBoolean).isFalse();
Converter<?> converter = mockOptionDef.getConverter();
assertThat(converter).isInstanceOf(StringConverter.class);
String value = (String) mockOptionDef.getDefaultValue();
assertThat(value).isEqualTo("strings");
// Expect reference equality, since we didn't recompute the value
Converter<?> secondConverter = mockOptionDef.getConverter();
assertThat(secondConverter).isSameInstanceAs(converter);
mockOptionDef.getDefaultValue();
// Verify that we didn't re-calculate the converter from the provided class object.
verify(mockOptionDef, times(1)).getProvidedConverter();
// The first call to getDefaultValue checks isSpecialNullDefault, which called
// getUnparsedValueDefault as well, but expect no more calls to it after the initial call.
verify(mockOptionDef, times(1)).isSpecialNullDefault();
verify(mockOptionDef, times(2)).getUnparsedDefaultValue();
}
/** Dummy options class, to test defaultValue handling. */
private static class DefaultValueTestOptions extends OptionsBase {
@Option(
name = "null_non_multiple_option",
defaultValue = "null",
documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
effectTags = OptionEffectTag.NO_OP)
public String nullNonMultipleOption;
@Option(
name = "null_multiple_option",
allowMultiple = true,
defaultValue = "null",
documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
effectTags = OptionEffectTag.NO_OP)
public List<String> nullMultipleOption;
@Option(
name = "empty_string_multiple_option",
allowMultiple = true,
defaultValue = "",
documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
effectTags = OptionEffectTag.NO_OP)
public List<String> emptyStringMultipleOption;
@Option(
name = "non_empty_string_multiple_option",
allowMultiple = true,
defaultValue = "text",
documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
effectTags = OptionEffectTag.NO_OP)
public List<String> nonEmptyStringMultipleOption;
}
@Test
public void specialDefaultValueForNonMultipleOptionShouldResultInNull() throws Exception {
// arrange
OptionDefinition optionDef =
OptionDefinition.extractOptionDefinition(
DefaultValueTestOptions.class.getField("nullNonMultipleOption"));
// act
Object result = optionDef.getDefaultValue();
// assert
assertThat(result).isNull();
}
@Test
@SuppressWarnings("unchecked")
public void specialDefaultValueForMultipleOptionShouldResultInEmptyList() throws Exception {
// arrange
OptionDefinition optionDef =
OptionDefinition.extractOptionDefinition(
DefaultValueTestOptions.class.getField("nullMultipleOption"));
// act
List<String> result = (List<String>) optionDef.getDefaultValue();
// assert
assertThat(result).isEmpty();
}
@Test
@SuppressWarnings("unchecked")
public void emptyStringForMultipleOptionShouldResultInEmptyList() throws Exception {
// arrange
OptionDefinition optionDef =
OptionDefinition.extractOptionDefinition(
DefaultValueTestOptions.class.getField("emptyStringMultipleOption"));
// act
List<String> result = (List<String>) optionDef.getDefaultValue();
// assert
assertThat(result).isEmpty();
}
@Test
@SuppressWarnings("unchecked")
public void nonEmptyStringForMultipleOptionShouldResultInEmptyList() throws Exception {
// arrange
OptionDefinition optionDef =
OptionDefinition.extractOptionDefinition(
DefaultValueTestOptions.class.getField("nonEmptyStringMultipleOption"));
// act
List<String> result = (List<String>) optionDef.getDefaultValue();
// assert
// TODO(b/138573276): this is a legacy behavior - assert the value is actually converted
assertThat(result).isEmpty();
}
}