View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.cli;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertFalse;
22  import static org.junit.jupiter.api.Assertions.assertNotEquals;
23  import static org.junit.jupiter.api.Assertions.assertNotSame;
24  import static org.junit.jupiter.api.Assertions.assertNull;
25  import static org.junit.jupiter.api.Assertions.assertThrows;
26  import static org.junit.jupiter.api.Assertions.assertTrue;
27  
28  import java.io.ByteArrayInputStream;
29  import java.io.ByteArrayOutputStream;
30  import java.io.IOException;
31  import java.io.ObjectInputStream;
32  import java.io.ObjectOutputStream;
33  
34  import org.junit.jupiter.api.Test;
35  
36  class OptionTest {
37  
38      private static final class DefaultOption extends Option {
39          private static final long serialVersionUID = 1L;
40  
41          private final String defaultValue;
42  
43          DefaultOption(final String opt, final String description, final String defaultValue) throws IllegalArgumentException {
44              super(opt, true, description);
45              this.defaultValue = defaultValue;
46          }
47  
48          @Override
49          public String getValue() {
50              return super.getValue() != null ? super.getValue() : defaultValue;
51          }
52      }
53  
54      private static final class TestOption extends Option {
55          private static final long serialVersionUID = 1L;
56  
57          TestOption(final String opt, final boolean hasArg, final String description) throws IllegalArgumentException {
58              super(opt, hasArg, description);
59          }
60  
61          @Override
62          public boolean addValue(final String value) {
63              processValue(value);
64              return true;
65          }
66      }
67  
68      private static void checkOption(final Option option, final String opt, final String description, final String longOpt, final int numArgs,
69              final String argName, final boolean required, final boolean optionalArg, final char valueSeparator, final Class<?> cls, final String deprecatedDesc,
70              final Boolean deprecatedForRemoval, final String deprecatedSince) {
71          assertEquals(opt, option.getOpt());
72          assertEquals(description, option.getDescription());
73          assertEquals(longOpt, option.getLongOpt());
74          assertEquals(numArgs, option.getArgs());
75          assertEquals(argName, option.getArgName());
76          assertEquals(required, option.isRequired());
77  
78          assertEquals(optionalArg, option.hasOptionalArg());
79          assertEquals(numArgs > 0, option.hasArg());
80          assertEquals(numArgs > 0, option.acceptsArg());
81          assertEquals(valueSeparator, option.getValueSeparator());
82          assertEquals(cls, option.getType());
83          if (deprecatedDesc != null) {
84              assertEquals(deprecatedDesc, option.getDeprecated().getDescription());
85          }
86          if (deprecatedForRemoval != null) {
87              assertEquals(deprecatedForRemoval, option.getDeprecated().isForRemoval());
88          }
89          if (deprecatedSince != null) {
90              assertEquals(deprecatedSince, option.getDeprecated().getSince());
91          }
92      }
93  
94      private Option roundTrip(final Option o) throws IOException, ClassNotFoundException {
95          final ByteArrayOutputStream baos = new ByteArrayOutputStream();
96          final ObjectOutputStream oos = new ObjectOutputStream(baos);
97          oos.writeObject(o);
98          final ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
99          final ObjectInputStream ois = new ObjectInputStream(bais);
100         return (Option) ois.readObject();
101     }
102 
103     @Test
104     void testAddValue() {
105         final Option option = new Option("f", null);
106         assertThrows(UnsupportedOperationException.class, () -> option.addValue(""));
107         assertThrows(IllegalStateException.class, () -> option.processValue(""));
108     }
109 
110     @Test
111     void testBuilderDeprecatedBuildEmpty() {
112         assertThrows(IllegalStateException.class, () -> Option.builder().build());
113     }
114 
115     @Test
116     void testBuilderEmpty() {
117         assertThrows(IllegalStateException.class, () -> Option.builder().get());
118     }
119 
120     @Test
121     void testBuilderInsufficientParams1() {
122         assertThrows(IllegalStateException.class, () -> Option.builder().desc("desc").get());
123     }
124 
125     @Test
126     void testBuilderInsufficientParams2() {
127         assertThrows(IllegalStateException.class, () -> Option.builder(null).desc("desc").get());
128     }
129 
130     @Test
131     void testBuilderInvalidOptionName0() {
132         assertThrows(IllegalStateException.class, () -> Option.builder().option(null).get());
133         assertThrows(IllegalArgumentException.class, () -> Option.builder().option(""));
134         assertThrows(IllegalArgumentException.class, () -> Option.builder().option(" "));
135     }
136 
137     @Test
138     void testBuilderInvalidOptionName1() {
139         assertThrows(IllegalArgumentException.class, () -> Option.builder().option("invalid?"));
140     }
141 
142     @Test
143     void testBuilderInvalidOptionName2() {
144         assertThrows(IllegalArgumentException.class, () -> Option.builder().option("invalid@"));
145     }
146 
147     @Test
148     void testBuilderInvalidOptionName3() {
149         assertThrows(IllegalArgumentException.class, () -> Option.builder("invalid?"));
150     }
151 
152     @Test
153     void testBuilderInvalidOptionName4() {
154         assertThrows(IllegalArgumentException.class, () -> Option.builder("invalid@"));
155     }
156 
157     @Test
158     void testBuilderMethods() {
159         final char defaultSeparator = (char) 0;
160 
161         checkOption(Option.builder("a").desc("desc").get(), "a", "desc", null, Option.UNINITIALIZED, null, false, false, defaultSeparator, String.class, null,
162                 null, null);
163         checkOption(Option.builder("a").desc("desc").get(), "a", "desc", null, Option.UNINITIALIZED, null, false, false, defaultSeparator, String.class, null,
164                 null, null);
165         checkOption(Option.builder("a").desc("desc").longOpt("aaa").get(), "a", "desc", "aaa", Option.UNINITIALIZED, null, false, false, defaultSeparator,
166                 String.class, null, null, null);
167         checkOption(Option.builder("a").desc("desc").hasArg(true).get(), "a", "desc", null, 1, null, false, false, defaultSeparator, String.class, null, null,
168                 null);
169         checkOption(Option.builder("a").desc("desc").hasArg(false).get(), "a", "desc", null, Option.UNINITIALIZED, null, false, false, defaultSeparator,
170                 String.class, null, null, null);
171         checkOption(Option.builder("a").desc("desc").hasArg(true).get(), "a", "desc", null, 1, null, false, false, defaultSeparator, String.class, null, null,
172                 null);
173         checkOption(Option.builder("a").desc("desc").numberOfArgs(3).get(), "a", "desc", null, 3, null, false, false, defaultSeparator, String.class, null,
174                 null, null);
175         checkOption(Option.builder("a").desc("desc").required(true).get(), "a", "desc", null, Option.UNINITIALIZED, null, true, false, defaultSeparator,
176                 String.class, null, null, null);
177         checkOption(Option.builder("a").desc("desc").required(false).get(), "a", "desc", null, Option.UNINITIALIZED, null, false, false, defaultSeparator,
178                 String.class, null, null, null);
179 
180         checkOption(Option.builder("a").desc("desc").argName("arg1").get(), "a", "desc", null, Option.UNINITIALIZED, "arg1", false, false, defaultSeparator,
181                 String.class, null, null, null);
182         checkOption(Option.builder("a").desc("desc").optionalArg(false).get(), "a", "desc", null, Option.UNINITIALIZED, null, false, false, defaultSeparator,
183                 String.class, null, null, null);
184         checkOption(Option.builder("a").desc("desc").optionalArg(true).get(), "a", "desc", null, 1, null, false, true, defaultSeparator, String.class, null,
185                 null, null);
186         checkOption(Option.builder("a").desc("desc").valueSeparator(':').get(), "a", "desc", null, Option.UNINITIALIZED, null, false, false, ':',
187                 String.class, null, null, null);
188         checkOption(Option.builder("a").desc("desc").type(Integer.class).get(), "a", "desc", null, Option.UNINITIALIZED, null, false, false, defaultSeparator,
189                 Integer.class, null, null, null);
190         checkOption(Option.builder("a").desc("desc").type(null).get(), "a", "desc", null, Option.UNINITIALIZED, null, false, false, defaultSeparator,
191                 String.class, null, null, null);
192         checkOption(Option.builder().option("a").desc("desc").type(Integer.class).get(), "a", "desc", null, Option.UNINITIALIZED, null, false, false,
193                 defaultSeparator, Integer.class, null, null, null);
194         // Deprecated
195         checkOption(Option.builder().option("a").desc("desc").type(Integer.class).deprecated().get(), "a", "desc", null, Option.UNINITIALIZED, null, false,
196                 false, defaultSeparator, Integer.class, "", false, "");
197         checkOption(Option.builder().option("a").desc("desc").type(Integer.class).deprecated(DeprecatedAttributes.builder().get()).get(), "a", "desc", null,
198                 Option.UNINITIALIZED, null, false, false, defaultSeparator, Integer.class, "", false, "");
199         checkOption(Option.builder().option("a").desc("desc").type(Integer.class).deprecated(DeprecatedAttributes.builder().setDescription("X").get()).get(),
200                 "a", "desc", null, Option.UNINITIALIZED, null, false, false, defaultSeparator, Integer.class, "X", false, "");
201         checkOption(
202                 Option.builder().option("a").desc("desc").type(Integer.class)
203                 .deprecated(DeprecatedAttributes.builder().setDescription("X").setForRemoval(true).get()).get(),
204                 "a", "desc", null, Option.UNINITIALIZED, null, false, false, defaultSeparator, Integer.class, "X", true, "");
205         checkOption(
206                 Option.builder().option("a").desc("desc").type(Integer.class)
207                 .deprecated(DeprecatedAttributes.builder().setDescription("X").setForRemoval(true).setSince("2.0").get()).get(),
208                 "a", "desc", null, Option.UNINITIALIZED, null, false, false, defaultSeparator, Integer.class, "X", true, "2.0");
209     }
210 
211     @Test
212     void testClear() {
213         final TestOption option = new TestOption("x", true, "");
214         assertTrue(option.getValuesList().isEmpty());
215         option.addValue("a");
216         assertEquals(1, option.getValuesList().size());
217         option.clearValues();
218         assertTrue(option.getValuesList().isEmpty());
219     }
220 
221     // See https://issues.apache.org/jira/browse/CLI-21
222     @Test
223     void testClone() {
224         final TestOption a = new TestOption("a", true, "");
225         final TestOption b = (TestOption) a.clone();
226         assertEquals(a, b);
227         assertNotSame(a, b);
228         a.setDescription("a");
229         assertEquals("", b.getDescription());
230         b.setArgs(2);
231         b.addValue("b1");
232         b.addValue("b2");
233         assertEquals(1, a.getArgs());
234         assertTrue(a.getValuesList().isEmpty());
235         assertEquals(2, b.getValues().length);
236     }
237 
238     @Test
239     void testEquals() {
240         final Option option1a = new Option("1", null);
241         final Option option1b = new Option("1", null);
242         final Option option2 = new Option("2", null);
243         assertEquals(option1a, option1a);
244         assertEquals(option1a, option1b);
245         assertEquals(option1b, option1a);
246         assertNotEquals(option1a, option2);
247         assertNotEquals(option1b, option2);
248         assertNotEquals(option2, option1a);
249         assertNotEquals(option2, "");
250     }
251 
252     @Test
253     void testGetValue() {
254         final Option option = new Option("f", null);
255         option.setArgs(Option.UNLIMITED_VALUES);
256 
257         assertEquals("default", option.getValue("default"));
258         assertNull(option.getValue(0));
259 
260         option.processValue("foo");
261 
262         assertEquals("foo", option.getValue());
263         assertEquals("foo", option.getValue(0));
264         assertEquals("foo", option.getValue("default"));
265     }
266 
267     @Test
268     void testHasArgName() {
269         final Option option = new Option("f", null);
270 
271         option.setArgName(null);
272         assertFalse(option.hasArgName());
273 
274         option.setArgName("");
275         assertFalse(option.hasArgName());
276 
277         option.setArgName("file");
278         assertTrue(option.hasArgName());
279     }
280 
281     @Test
282     void testHasArgs() {
283         final Option option = new Option("f", null);
284 
285         option.setArgs(0);
286         assertFalse(option.hasArgs());
287 
288         option.setArgs(1);
289         assertFalse(option.hasArgs());
290 
291         option.setArgs(10);
292         assertTrue(option.hasArgs());
293 
294         option.setArgs(Option.UNLIMITED_VALUES);
295         assertTrue(option.hasArgs());
296 
297         option.setArgs(Option.UNINITIALIZED);
298         assertFalse(option.hasArgs());
299     }
300 
301     @Test
302     void testHashCode() {
303         assertNotEquals(Option.builder("test").get().hashCode(), Option.builder("test2").get().hashCode());
304         assertNotEquals(Option.builder("test").get().hashCode(), Option.builder().longOpt("test").get().hashCode());
305         assertNotEquals(Option.builder("test").get().hashCode(), Option.builder("test").longOpt("long test").get().hashCode());
306     }
307 
308     @Test
309     public void testProcessValue() {
310         final Option option = new Option("D", true, "Define property");
311         option.setValueSeparator('=');
312         final NullPointerException exception = assertThrows(NullPointerException.class, () -> option.processValue(null));
313         assertTrue(exception.getMessage().contains("value"));
314     }
315 
316     @Test
317     void testSerialization() throws IOException, ClassNotFoundException {
318         final Option option = Option.builder("o").type(TypeHandlerTest.Instantiable.class).get();
319         assertEquals(Converter.DEFAULT, option.getConverter());
320         Option roundtrip = roundTrip(option);
321         assertEquals(Converter.DEFAULT, roundtrip.getConverter());
322         // verify unregistered class converters and verifiers get reset to default.
323         // converters are NOT Serializable, use a serialization proxy if you want that.
324         option.setConverter(Converter.DATE);
325         roundtrip = roundTrip(option);
326         assertEquals(Converter.DEFAULT, roundtrip.getConverter());
327         // verify registered class converters and verifiers do not get reset to default.
328         // converters are NOT Serializable, use a serialization proxy if you want that.
329         // verify earlier values still set.
330         assertEquals(Converter.DATE, option.getConverter());
331         roundtrip = roundTrip(option);
332         assertEquals(Converter.DEFAULT, roundtrip.getConverter());
333     }
334 
335     @Test
336     void testSubclass() {
337         final Option option = new DefaultOption("f", "file", "myfile.txt");
338         final Option clone = (Option) option.clone();
339         assertEquals("myfile.txt", clone.getValue());
340         assertEquals(DefaultOption.class, clone.getClass());
341     }
342 
343     @Test
344     void testTypeClass() {
345         final Option option = new Option("f", null);
346         assertEquals(String.class, option.getType());
347         option.setType(CharSequence.class);
348         assertEquals(CharSequence.class, option.getType());
349     }
350 
351     @Test
352     void testTypeObject() {
353         final Option option = new Option("f", null);
354         assertEquals(String.class, option.getType());
355         @SuppressWarnings("cast")
356         final Object type = CharSequence.class; // Do NOT remove cast
357         option.setType(type);
358         assertEquals(CharSequence.class, option.getType());
359     }
360 }