aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/test/java/com/google/devtools/common/options/OptionDefinitionTest.java
blob: eb1ae0d6a613eeda45c806b75bd0683d24e57fb0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// 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.fail;
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.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"));
    try {
      optionDef.getDefaultValue();
      fail("Incorrect default should have caused getDefaultValue to fail.");
    } catch (ConstructionException e) {
      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 {
    try {
      OptionDefinition.extractOptionDefinition(BrokenOptions.class.getField("notAnOption"));
      fail("notAnOption isn't an Option, and shouldn't be accepted as one.");
    } catch (NotAnOptionException e) {
      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).isSameAs(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).isSameAs(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();
  }
}