blob: 9ab340edf55ccd9503f51cc3d8ad337f79fb072e [file] [log] [blame]
brandjon4db5c962017-04-17 17:29:32 +02001// Copyright 2017 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.devtools.common.options;
16
17import static com.google.common.truth.Truth.assertThat;
jcater20e631b2019-04-29 13:29:12 -070018import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows;
brandjon4db5c962017-04-17 17:29:32 +020019
20import com.google.common.collect.ImmutableList;
ccalvarin987f09f2017-08-31 19:50:39 +020021import com.google.common.truth.Correspondence;
ccalvarin80399bc2017-09-08 20:43:02 +020022import com.google.devtools.common.options.OptionsParser.ConstructionException;
brandjon91e4ded2017-04-17 21:52:58 +020023import java.util.ArrayList;
brandjon4db5c962017-04-17 17:29:32 +020024import java.util.List;
brandjon91e4ded2017-04-17 21:52:58 +020025import java.util.Map;
brandjon4db5c962017-04-17 17:29:32 +020026import org.junit.Test;
27import org.junit.runner.RunWith;
28import org.junit.runners.JUnit4;
29
30/** Tests for {@link IsolatedOptionsData} and {@link OptionsData}. */
31@RunWith(JUnit4.class)
32public class OptionsDataTest {
33
34 private static IsolatedOptionsData construct(Class<? extends OptionsBase> optionsClass)
35 throws OptionsParser.ConstructionException {
36 return IsolatedOptionsData.from(ImmutableList.<Class<? extends OptionsBase>>of(optionsClass));
37 }
38
39 private static IsolatedOptionsData construct(
brandjon91e4ded2017-04-17 21:52:58 +020040 Class<? extends OptionsBase> optionsClass1,
41 Class<? extends OptionsBase> optionsClass2)
brandjon4db5c962017-04-17 17:29:32 +020042 throws OptionsParser.ConstructionException {
43 return IsolatedOptionsData.from(
44 ImmutableList.<Class<? extends OptionsBase>>of(optionsClass1, optionsClass2));
45 }
46
brandjon91e4ded2017-04-17 21:52:58 +020047 private static IsolatedOptionsData construct(
48 Class<? extends OptionsBase> optionsClass1,
49 Class<? extends OptionsBase> optionsClass2,
50 Class<? extends OptionsBase> optionsClass3)
51 throws OptionsParser.ConstructionException {
52 return IsolatedOptionsData.from(
53 ImmutableList.<Class<? extends OptionsBase>>of(
54 optionsClass1, optionsClass2, optionsClass3));
55 }
56
57 /** Dummy options class. */
brandjon4db5c962017-04-17 17:29:32 +020058 public static class ExampleNameConflictOptions extends OptionsBase {
59 @Option(
60 name = "foo",
ccalvarin835e8e32017-06-29 17:05:59 +020061 documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
62 effectTags = {OptionEffectTag.NO_OP},
brandjon4db5c962017-04-17 17:29:32 +020063 defaultValue = "1"
64 )
65 public int foo;
66
67 @Option(
68 name = "foo",
ccalvarin835e8e32017-06-29 17:05:59 +020069 documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
70 effectTags = {OptionEffectTag.NO_OP},
brandjon4db5c962017-04-17 17:29:32 +020071 defaultValue = "I should conflict with foo"
72 )
73 public String anotherFoo;
74 }
75
76 @Test
77 public void testNameConflictInSingleClass() {
jcater20e631b2019-04-29 13:29:12 -070078 ConstructionException e =
79 assertThrows(
80 "foo should conflict with the previous flag foo",
81 ConstructionException.class,
82 () -> construct(ExampleNameConflictOptions.class));
83 assertThat(e).hasCauseThat().isInstanceOf(DuplicateOptionDeclarationException.class);
84 assertThat(e)
85 .hasMessageThat()
86 .contains("Duplicate option name, due to option name collision: --foo");
brandjon4db5c962017-04-17 17:29:32 +020087 }
88
brandjon91e4ded2017-04-17 21:52:58 +020089 /** Dummy options class. */
brandjon4db5c962017-04-17 17:29:32 +020090 public static class ExampleIntegerFooOptions extends OptionsBase {
91 @Option(
92 name = "foo",
ccalvarin835e8e32017-06-29 17:05:59 +020093 documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
94 effectTags = {OptionEffectTag.NO_OP},
brandjon4db5c962017-04-17 17:29:32 +020095 defaultValue = "5"
96 )
97 public int foo;
98 }
99
brandjon91e4ded2017-04-17 21:52:58 +0200100 /** Dummy options class. */
brandjon4db5c962017-04-17 17:29:32 +0200101 public static class ExampleBooleanFooOptions extends OptionsBase {
102 @Option(
103 name = "foo",
ccalvarin835e8e32017-06-29 17:05:59 +0200104 documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
105 effectTags = {OptionEffectTag.NO_OP},
brandjon4db5c962017-04-17 17:29:32 +0200106 defaultValue = "false"
107 )
108 public boolean foo;
109 }
110
111 @Test
112 public void testNameConflictInTwoClasses() {
jcater20e631b2019-04-29 13:29:12 -0700113 ConstructionException e =
114 assertThrows(
115 "foo should conflict with the previous flag foo",
116 ConstructionException.class,
117 () -> construct(ExampleIntegerFooOptions.class, ExampleBooleanFooOptions.class));
118 assertThat(e).hasCauseThat().isInstanceOf(DuplicateOptionDeclarationException.class);
119 assertThat(e)
120 .hasMessageThat()
121 .contains("Duplicate option name, due to option name collision: --foo");
brandjon4db5c962017-04-17 17:29:32 +0200122 }
123
brandjon91e4ded2017-04-17 21:52:58 +0200124 /** Dummy options class. */
ccalvarin80399bc2017-09-08 20:43:02 +0200125 public static class ExamplePrefixedFooOptions extends OptionsBase {
brandjon4db5c962017-04-17 17:29:32 +0200126 @Option(
127 name = "nofoo",
ccalvarin835e8e32017-06-29 17:05:59 +0200128 documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
129 effectTags = {OptionEffectTag.NO_OP},
brandjon4db5c962017-04-17 17:29:32 +0200130 defaultValue = "false"
131 )
132 public boolean noFoo;
133 }
134
135 @Test
136 public void testBooleanPrefixNameConflict() {
137 // Try the same test in both orders, the parser should fail if the overlapping flag is defined
138 // before or after the boolean flag introduces the alias.
jcater20e631b2019-04-29 13:29:12 -0700139 ConstructionException e =
140 assertThrows(
141 "nofoo should conflict with the previous flag foo, "
142 + "since foo, as a boolean flag, can be written as --nofoo",
143 ConstructionException.class,
144 () -> construct(ExampleBooleanFooOptions.class, ExamplePrefixedFooOptions.class));
145 assertThat(e).hasCauseThat().isInstanceOf(DuplicateOptionDeclarationException.class);
146 assertThat(e)
147 .hasMessageThat()
148 .contains(
149 "Duplicate option name, due to option --nofoo, it "
150 + "conflicts with a negating alias for boolean flag --foo");
brandjon4db5c962017-04-17 17:29:32 +0200151
jcater20e631b2019-04-29 13:29:12 -0700152 e =
153 assertThrows(
154 "option nofoo should conflict with the previous flag foo, "
155 + "since foo, as a boolean flag, can be written as --nofoo",
156 ConstructionException.class,
157 () -> construct(ExamplePrefixedFooOptions.class, ExampleBooleanFooOptions.class));
158 assertThat(e).hasCauseThat().isInstanceOf(DuplicateOptionDeclarationException.class);
159 assertThat(e)
160 .hasMessageThat()
161 .contains("Duplicate option name, due to boolean option alias: --nofoo");
brandjon4db5c962017-04-17 17:29:32 +0200162 }
163
brandjon91e4ded2017-04-17 21:52:58 +0200164 /** Dummy options class. */
brandjon4db5c962017-04-17 17:29:32 +0200165 public static class ExampleBarWasNamedFooOption extends OptionsBase {
166 @Option(
167 name = "bar",
168 oldName = "foo",
ccalvarin835e8e32017-06-29 17:05:59 +0200169 documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
170 effectTags = {OptionEffectTag.NO_OP},
brandjon4db5c962017-04-17 17:29:32 +0200171 defaultValue = "false"
172 )
173 public boolean bar;
174 }
175
176 @Test
177 public void testBooleanAliasWithOldNameConflict() {
178 // Try the same test in both orders, the parser should fail if the overlapping flag is defined
179 // before or after the boolean flag introduces the alias.
jcater20e631b2019-04-29 13:29:12 -0700180 ConstructionException e =
181 assertThrows(
182 "bar has old name foo, which is a boolean flag and can be named as nofoo, so it "
183 + "should conflict with the previous option --nofoo",
184 ConstructionException.class,
185 () -> construct(ExamplePrefixedFooOptions.class, ExampleBarWasNamedFooOption.class));
186 assertThat(e).hasCauseThat().isInstanceOf(DuplicateOptionDeclarationException.class);
187 assertThat(e)
188 .hasMessageThat()
189 .contains("Duplicate option name, due to boolean option alias: --nofoo");
ccalvarin80399bc2017-09-08 20:43:02 +0200190
jcater20e631b2019-04-29 13:29:12 -0700191 e =
192 assertThrows(
193 "nofoo should conflict with the previous flag bar that has old name foo, "
194 + "since foo, as a boolean flag, can be written as --nofoo",
195 ConstructionException.class,
196 () -> construct(ExampleBarWasNamedFooOption.class, ExamplePrefixedFooOptions.class));
197 assertThat(e).hasCauseThat().isInstanceOf(DuplicateOptionDeclarationException.class);
198 assertThat(e)
199 .hasMessageThat()
200 .contains(
201 "Duplicate option name, due to option --nofoo, it conflicts with a negating "
202 + "alias for boolean flag --foo");
brandjon4db5c962017-04-17 17:29:32 +0200203 }
204
brandjon91e4ded2017-04-17 21:52:58 +0200205 /** Dummy options class. */
brandjon4db5c962017-04-17 17:29:32 +0200206 public static class ExampleBarWasNamedNoFooOption extends OptionsBase {
207 @Option(
208 name = "bar",
209 oldName = "nofoo",
ccalvarin835e8e32017-06-29 17:05:59 +0200210 documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
211 effectTags = {OptionEffectTag.NO_OP},
brandjon4db5c962017-04-17 17:29:32 +0200212 defaultValue = "false"
213 )
214 public boolean bar;
215 }
216
217 @Test
218 public void testBooleanWithOldNameAsAliasOfBooleanConflict() {
219 // Try the same test in both orders, the parser should fail if the overlapping flag is defined
220 // before or after the boolean flag introduces the alias.
jcater20e631b2019-04-29 13:29:12 -0700221 ConstructionException e =
222 assertThrows(
223 "nofoo, the old name for bar, should conflict with the previous flag foo, "
224 + "since foo, as a boolean flag, can be written as --nofoo",
225 ConstructionException.class,
226 () -> construct(ExampleBooleanFooOptions.class, ExampleBarWasNamedNoFooOption.class));
227 assertThat(e).hasCauseThat().isInstanceOf(DuplicateOptionDeclarationException.class);
228 assertThat(e)
229 .hasMessageThat()
230 .contains(
231 "Duplicate option name, due to old option name --nofoo, it conflicts with a "
232 + "negating alias for boolean flag --foo");
ccalvarin80399bc2017-09-08 20:43:02 +0200233
jcater20e631b2019-04-29 13:29:12 -0700234 e =
235 assertThrows(
236 "foo, as a boolean flag, can be written as --nofoo and should conflict with the "
237 + "previous option bar that has old name nofoo",
238 ConstructionException.class,
239 () -> construct(ExampleBarWasNamedNoFooOption.class, ExampleBooleanFooOptions.class));
240 assertThat(e).hasCauseThat().isInstanceOf(DuplicateOptionDeclarationException.class);
241 assertThat(e)
242 .hasMessageThat()
243 .contains("Duplicate option name, due to boolean option alias: --nofoo");
brandjon4db5c962017-04-17 17:29:32 +0200244 }
245
brandjon91e4ded2017-04-17 21:52:58 +0200246 /** Dummy options class. */
ccalvarin80399bc2017-09-08 20:43:02 +0200247 public static class ExampleFooBooleanConflictsWithOwnOldName extends OptionsBase {
248 @Option(
249 name = "nofoo",
250 oldName = "foo",
251 documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
252 effectTags = {OptionEffectTag.NO_OP},
253 defaultValue = "false"
254 )
255 public boolean foo;
256 }
257
258 @Test
259 public void testSelfConflictBooleanAliases() {
260 // Try the same test in both orders, the parser should fail if the overlapping flag is defined
261 // before or after the boolean flag introduces the alias.
jcater20e631b2019-04-29 13:29:12 -0700262 ConstructionException e =
263 assertThrows(
264 "foo, the old name for boolean option nofoo, should conflict with its own new name.",
265 ConstructionException.class,
266 () -> construct(ExampleFooBooleanConflictsWithOwnOldName.class));
267 assertThat(e).hasCauseThat().isInstanceOf(DuplicateOptionDeclarationException.class);
268 assertThat(e)
269 .hasMessageThat()
270 .contains("Duplicate option name, due to boolean option alias: --nofoo");
ccalvarin80399bc2017-09-08 20:43:02 +0200271 }
272
273 /** Dummy options class. */
274 public static class OldNameToCanonicalNameConflictExample extends OptionsBase {
brandjon4db5c962017-04-17 17:29:32 +0200275 @Option(
276 name = "new_name",
277 oldName = "old_name",
ccalvarin835e8e32017-06-29 17:05:59 +0200278 documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
279 effectTags = {OptionEffectTag.NO_OP},
brandjon4db5c962017-04-17 17:29:32 +0200280 defaultValue = "defaultValue"
281 )
282 public String flag1;
283
284 @Option(
285 name = "old_name",
ccalvarin835e8e32017-06-29 17:05:59 +0200286 documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
287 effectTags = {OptionEffectTag.NO_OP},
brandjon4db5c962017-04-17 17:29:32 +0200288 defaultValue = "defaultValue"
289 )
290 public String flag2;
291 }
292
293 @Test
ccalvarin80399bc2017-09-08 20:43:02 +0200294 public void testOldNameToCanonicalNameConflict() {
jcater20e631b2019-04-29 13:29:12 -0700295 ConstructionException expected =
296 assertThrows(
297 "old_name should conflict with the flag already named old_name",
298 ConstructionException.class,
299 () -> construct(OldNameToCanonicalNameConflictExample.class));
300 assertThat(expected).hasCauseThat().isInstanceOf(DuplicateOptionDeclarationException.class);
301 assertThat(expected)
302 .hasMessageThat()
303 .contains(
304 "Duplicate option name, due to option name collision with another option's old name:"
305 + " --old_name");
ccalvarin80399bc2017-09-08 20:43:02 +0200306 }
307
308 /** Dummy options class. */
309 public static class OldNameConflictExample extends OptionsBase {
310 @Option(
311 name = "new_name",
312 oldName = "old_name",
313 documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
314 effectTags = {OptionEffectTag.NO_OP},
315 defaultValue = "defaultValue"
316 )
317 public String flag1;
318
319 @Option(
320 name = "another_name",
321 oldName = "old_name",
322 documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
323 effectTags = {OptionEffectTag.NO_OP},
324 defaultValue = "defaultValue"
325 )
326 public String flag2;
327 }
328
329 @Test
330 public void testOldNameToOldNameConflict() {
jcater20e631b2019-04-29 13:29:12 -0700331 ConstructionException expected =
332 assertThrows(
333 "old_name should conflict with the flag already named old_name",
334 ConstructionException.class,
335 () -> construct(OldNameConflictExample.class));
336 assertThat(expected).hasCauseThat().isInstanceOf(DuplicateOptionDeclarationException.class);
337 assertThat(expected)
338 .hasMessageThat()
339 .contains(
340 "Duplicate option name, due to old option name collision with another "
341 + "old option name: --old_name");
brandjon4db5c962017-04-17 17:29:32 +0200342 }
343
brandjon91e4ded2017-04-17 21:52:58 +0200344 /** Dummy options class. */
brandjon4db5c962017-04-17 17:29:32 +0200345 public static class StringConverter implements Converter<String> {
346 @Override
347 public String convert(String input) {
348 return input;
349 }
350
351 @Override
352 public String getTypeDescription() {
353 return "a string";
354 }
355 }
356
brandjon91e4ded2017-04-17 21:52:58 +0200357 /**
358 * Dummy options class.
359 *
360 * <p>Option name order is different from field name order.
361 *
362 * <p>There are four fields to increase the likelihood of a non-deterministic order being noticed.
363 */
364 public static class FieldNamesDifferOptions extends OptionsBase {
365
366 @Option(
367 name = "foo",
ccalvarin835e8e32017-06-29 17:05:59 +0200368 documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
369 effectTags = {OptionEffectTag.NO_OP},
brandjon91e4ded2017-04-17 21:52:58 +0200370 defaultValue = "0"
371 )
372 public int aFoo;
373
374 @Option(
375 name = "bar",
ccalvarin835e8e32017-06-29 17:05:59 +0200376 documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
377 effectTags = {OptionEffectTag.NO_OP},
brandjon91e4ded2017-04-17 21:52:58 +0200378 defaultValue = "0"
379 )
380 public int bBar;
381
382 @Option(
383 name = "baz",
ccalvarin835e8e32017-06-29 17:05:59 +0200384 documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
385 effectTags = {OptionEffectTag.NO_OP},
brandjon91e4ded2017-04-17 21:52:58 +0200386 defaultValue = "0"
387 )
388 public int cBaz;
389
390 @Option(
391 name = "qux",
ccalvarin835e8e32017-06-29 17:05:59 +0200392 documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
393 effectTags = {OptionEffectTag.NO_OP},
brandjon91e4ded2017-04-17 21:52:58 +0200394 defaultValue = "0"
395 )
396 public int dQux;
397 }
398
399 /** Dummy options class. */
400 public static class EndOfAlphabetOptions extends OptionsBase {
401 @Option(
402 name = "X",
ccalvarin835e8e32017-06-29 17:05:59 +0200403 documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
404 effectTags = {OptionEffectTag.NO_OP},
brandjon91e4ded2017-04-17 21:52:58 +0200405 defaultValue = "0"
406 )
407 public int x;
408
409 @Option(
410 name = "Y",
ccalvarin835e8e32017-06-29 17:05:59 +0200411 documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
412 effectTags = {OptionEffectTag.NO_OP},
brandjon91e4ded2017-04-17 21:52:58 +0200413 defaultValue = "0"
414 )
415 public int y;
416 }
417
418 /** Dummy options class. */
419 public static class ReverseOrderedOptions extends OptionsBase {
420 @Option(
421 name = "C",
ccalvarin835e8e32017-06-29 17:05:59 +0200422 documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
423 effectTags = {OptionEffectTag.NO_OP},
brandjon91e4ded2017-04-17 21:52:58 +0200424 defaultValue = "0"
425 )
426 public int c;
427
428 @Option(
429 name = "B",
ccalvarin835e8e32017-06-29 17:05:59 +0200430 documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
431 effectTags = {OptionEffectTag.NO_OP},
brandjon91e4ded2017-04-17 21:52:58 +0200432 defaultValue = "0"
433 )
434 public int b;
435
436 @Option(
437 name = "A",
ccalvarin835e8e32017-06-29 17:05:59 +0200438 documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
439 effectTags = {OptionEffectTag.NO_OP},
brandjon91e4ded2017-04-17 21:52:58 +0200440 defaultValue = "0"
441 )
442 public int a;
443 }
444
445 @Test
446 public void optionsClassesIsOrdered() throws Exception {
447 IsolatedOptionsData data = construct(
448 FieldNamesDifferOptions.class,
449 EndOfAlphabetOptions.class,
450 ReverseOrderedOptions.class);
451 assertThat(data.getOptionsClasses()).containsExactly(
452 FieldNamesDifferOptions.class,
453 EndOfAlphabetOptions.class,
454 ReverseOrderedOptions.class).inOrder();
455 }
456
457 @Test
458 public void getAllNamedFieldsIsOrdered() throws Exception {
459 IsolatedOptionsData data = construct(
460 FieldNamesDifferOptions.class,
461 EndOfAlphabetOptions.class,
462 ReverseOrderedOptions.class);
463 ArrayList<String> names = new ArrayList<>();
ccalvarin80399bc2017-09-08 20:43:02 +0200464 for (Map.Entry<String, OptionDefinition> entry : data.getAllOptionDefinitions()) {
brandjon91e4ded2017-04-17 21:52:58 +0200465 names.add(entry.getKey());
466 }
467 assertThat(names).containsExactly(
468 "bar", "baz", "foo", "qux", "X", "Y", "A", "B", "C").inOrder();
469 }
470
ccalvarin987f09f2017-08-31 19:50:39 +0200471 private List<String> getOptionNames(Class<? extends OptionsBase> optionsBase) {
brandjon91e4ded2017-04-17 21:52:58 +0200472 ArrayList<String> result = new ArrayList<>();
ccalvarin987f09f2017-08-31 19:50:39 +0200473 for (OptionDefinition optionDefinition :
474 OptionsData.getAllOptionDefinitionsForClass(optionsBase)) {
ccalvarine8aae032017-08-22 07:17:44 +0200475 result.add(optionDefinition.getOptionName());
brandjon91e4ded2017-04-17 21:52:58 +0200476 }
477 return result;
478 }
479
480 @Test
481 public void getFieldsForClassIsOrdered() throws Exception {
ccalvarin987f09f2017-08-31 19:50:39 +0200482 assertThat(getOptionNames(FieldNamesDifferOptions.class))
ccalvarine8aae032017-08-22 07:17:44 +0200483 .containsExactly("bar", "baz", "foo", "qux")
484 .inOrder();
ccalvarin987f09f2017-08-31 19:50:39 +0200485 assertThat(getOptionNames(EndOfAlphabetOptions.class)).containsExactly("X", "Y").inOrder();
486 assertThat(getOptionNames(ReverseOrderedOptions.class))
ccalvarine8aae032017-08-22 07:17:44 +0200487 .containsExactly("A", "B", "C")
488 .inOrder();
brandjon91e4ded2017-04-17 21:52:58 +0200489 }
ccalvarin987715b2017-06-07 09:17:35 -0400490
ccalvarin987f09f2017-08-31 19:50:39 +0200491 @Test
492 public void optionsDefinitionsAreSharedBetweenOptionsBases() throws Exception {
493 Class<FieldNamesDifferOptions> class1 = FieldNamesDifferOptions.class;
494 Class<EndOfAlphabetOptions> class2 = EndOfAlphabetOptions.class;
495 Class<ReverseOrderedOptions> class3 = ReverseOrderedOptions.class;
496
497 // Construct the definitions once and accumulate them so we can test that these are not
498 // recomputed during the construction of the options data.
499 ImmutableList<OptionDefinition> optionDefinitions =
500 new ImmutableList.Builder<OptionDefinition>()
501 .addAll(OptionsData.getAllOptionDefinitionsForClass(class1))
502 .addAll(OptionsData.getAllOptionDefinitionsForClass(class2))
503 .addAll(OptionsData.getAllOptionDefinitionsForClass(class3))
504 .build();
505
506 // Construct the data all together.
507 IsolatedOptionsData data = construct(class1, class2, class3);
508 ArrayList<OptionDefinition> optionDefinitionsFromData =
509 new ArrayList<>(optionDefinitions.size());
ccalvarin80399bc2017-09-08 20:43:02 +0200510 data.getAllOptionDefinitions()
511 .forEach(entry -> optionDefinitionsFromData.add(entry.getValue()));
ccalvarin987f09f2017-08-31 19:50:39 +0200512
cpovirk3b20f1f2019-05-24 15:26:31 -0700513 Correspondence<Object, Object> referenceEquality =
514 Correspondence.from((obj1, obj2) -> obj1 == obj2, "is the same object as");
ccalvarin987f09f2017-08-31 19:50:39 +0200515 assertThat(optionDefinitionsFromData)
516 .comparingElementsUsing(referenceEquality)
cpovirk58f736c2019-06-05 11:37:01 -0700517 .containsAtLeastElementsIn(optionDefinitions);
ccalvarin987f09f2017-08-31 19:50:39 +0200518
519 // Construct options data for each class separately, and check again.
520 IsolatedOptionsData data1 = construct(class1);
521 IsolatedOptionsData data2 = construct(class2);
522 IsolatedOptionsData data3 = construct(class3);
523 ArrayList<OptionDefinition> optionDefinitionsFromGroupedData =
524 new ArrayList<>(optionDefinitions.size());
525 data1
ccalvarin80399bc2017-09-08 20:43:02 +0200526 .getAllOptionDefinitions()
ccalvarin987f09f2017-08-31 19:50:39 +0200527 .forEach(entry -> optionDefinitionsFromGroupedData.add(entry.getValue()));
528 data2
ccalvarin80399bc2017-09-08 20:43:02 +0200529 .getAllOptionDefinitions()
ccalvarin987f09f2017-08-31 19:50:39 +0200530 .forEach(entry -> optionDefinitionsFromGroupedData.add(entry.getValue()));
531 data3
ccalvarin80399bc2017-09-08 20:43:02 +0200532 .getAllOptionDefinitions()
ccalvarin987f09f2017-08-31 19:50:39 +0200533 .forEach(entry -> optionDefinitionsFromGroupedData.add(entry.getValue()));
534
535 assertThat(optionDefinitionsFromGroupedData)
536 .comparingElementsUsing(referenceEquality)
cpovirk58f736c2019-06-05 11:37:01 -0700537 .containsAtLeastElementsIn(optionDefinitions);
ccalvarin987f09f2017-08-31 19:50:39 +0200538 }
539
ccalvarin987715b2017-06-07 09:17:35 -0400540 /** Dummy options class. */
ccalvarin987715b2017-06-07 09:17:35 -0400541 public static class ValidExpansionOptions extends OptionsBase {
ccalvarin835e8e32017-06-29 17:05:59 +0200542 @Option(
543 name = "foo",
544 documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
545 effectTags = {OptionEffectTag.NO_OP},
546 defaultValue = "1"
547 )
ccalvarin987715b2017-06-07 09:17:35 -0400548 public int foo;
549
550 @Option(
551 name = "bar",
ccalvarin835e8e32017-06-29 17:05:59 +0200552 documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
553 effectTags = {OptionEffectTag.NO_OP},
ccalvarin987715b2017-06-07 09:17:35 -0400554 defaultValue = "null",
555 expansion = {"--foo=42"}
556 )
557 public Void bar;
558 }
559
560 @Test
561 public void staticExpansionOptionsCanBeVoidType() {
562 construct(ValidExpansionOptions.class);
563 }
brandjon4db5c962017-04-17 17:29:32 +0200564}