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         http://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.assertThrows;
23  
24  import java.util.ArrayList;
25  import java.util.List;
26  import java.util.stream.Stream;
27  
28  import org.junit.jupiter.api.BeforeAll;
29  import org.junit.jupiter.api.Test;
30  import org.junit.jupiter.params.ParameterizedTest;
31  import org.junit.jupiter.params.provider.Arguments;
32  import org.junit.jupiter.params.provider.MethodSource;
33  
34  public class OptionValidatorTest {
35  
36      /*
37       * Exemplars of various types of characters
38       */
39  
40      private static final String LETTERS = "a\u00D1"; // a and Ñ
41  
42      // '\u0660' through '\u0669', Arabic-Indic digits, '\u06F0' through '\u06F9',
43      // Extended Arabic-Indic digits
44      // '\u0966' through '\u096F', Devanagari digits, '\uFF10' through '\uFF19',
45      // Fullwidth digits
46      private static final String DIGITS = "1\u0661\u06f2\u0968\uFF14";
47  
48      private static final String CURRENCY = "€$";
49  
50      // this is the complete puncutation set do not modify it as Character.isJavaIdentifierPart filters
51      // the good and bad ones out in the setup.
52      private static final String PUNCTUATION = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
53  
54      private static final String COMBINING_MARK = "\u0303";
55  
56      private static final String NON_SPACING_MARK = "\u0CBF";
57  
58      private static final String IDENTIFIER_IGNORABLE = "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008";
59  
60      private static String acceptablePunctuation;
61  
62      private static String notAcceptablePunctuation;
63  
64      private static String additionalOptonChars;
65      private static String additionalLongChars;
66  
67      private static String firstChars;
68      private static String notFirstChars;
69  
70      private static String restChars;
71      private static String notRestChars;
72  
73      private static Stream<Arguments> optionParameters() {
74  
75          final List<Arguments> args = new ArrayList<>();
76  
77          args.add(Arguments.of("CamelCase", true, "Camel case error"));
78          args.add(Arguments.of("Snake_case", true, "Snake case error"));
79          args.add(Arguments.of("_leadingUnderscore", true, "Leading underscore error"));
80          args.add(Arguments.of("kabob-case", true, "Kabob case error"));
81          args.add(Arguments.of("-leadingDash", false, "Leading dash error"));
82          args.add(Arguments.of("lowercase", true, "Lower case error"));
83          args.add(Arguments.of("UPPERCASE", true, "Upper case error"));
84  
85          // build passing test cases
86          for (final char c : firstChars.toCharArray()) {
87              final String s = String.format("%sMoreText", c);
88              args.add(Arguments.of(s, true, String.format("testing: First character '%s'", c)));
89          }
90  
91          for (final char c : restChars.toCharArray()) {
92              final String s = String.format("Some%sText", c);
93              args.add(Arguments.of(s, true, String.format("testing: Middle character '%s'", c)));
94          }
95  
96          // build failing test cases
97          for (final char c : notFirstChars.toCharArray()) {
98              final String s = String.format("%sMoreText", c);
99              args.add(Arguments.of(s, false, String.format("testing: Bad first character '%s'", c)));
100         }
101 
102         for (final char c : notRestChars.toCharArray()) {
103             final String s = String.format("Some%sText", c);
104             args.add(Arguments.of(s, false, String.format("testing: Bad middle character '%s'", c)));
105         }
106 
107         return args.stream();
108     }
109 
110     @BeforeAll
111     public static void setup() {
112         StringBuilder sb = new StringBuilder();
113         final StringBuilder sb2 = new StringBuilder();
114         int idx;
115 
116         for (final char c : PUNCTUATION.toCharArray()) {
117             if (Character.isJavaIdentifierPart(c)) {
118                 sb.append(c);
119             } else {
120                 sb2.append(c);
121             }
122         }
123         acceptablePunctuation = sb.toString();
124         notAcceptablePunctuation = sb2.toString();
125 
126         sb = new StringBuilder();
127         for (final char c : OptionValidator.ADDITIONAL_LONG_CHARS) {
128             sb.append(c);
129         }
130         additionalLongChars = sb.toString();
131 
132         sb = new StringBuilder();
133         for (final char c : OptionValidator.ADDITIONAL_OPTION_CHARS) {
134             sb.append(c);
135         }
136         additionalOptonChars = sb.toString();
137 
138         final String javaIdentifierPart = LETTERS + DIGITS + CURRENCY + acceptablePunctuation + COMBINING_MARK
139                 + NON_SPACING_MARK + IDENTIFIER_IGNORABLE;
140 
141         firstChars = additionalOptonChars + javaIdentifierPart;
142 
143         sb = new StringBuilder(notAcceptablePunctuation).append(additionalLongChars);
144         for (final char c : OptionValidator.ADDITIONAL_OPTION_CHARS) {
145             while ((idx = sb.indexOf(Character.toString(c))) > -1) {
146                 sb.deleteCharAt(idx);
147             }
148         }
149         notFirstChars = sb.toString();
150 
151         restChars = additionalLongChars + javaIdentifierPart;
152         sb = new StringBuilder(notAcceptablePunctuation).append(additionalOptonChars);
153         for (final char c : OptionValidator.ADDITIONAL_LONG_CHARS) {
154             while ((idx = sb.indexOf(Character.toString(c))) > -1) {
155                 sb.deleteCharAt(idx);
156             }
157         }
158         notRestChars = sb.toString();
159 
160     }
161 
162     @Test
163     public void testExclusivity() {
164         /* since we modify acceptable chars by add and removing ADDITIONAL* chars we must verify that they do not exist in the
165          * base javaIdentiferPart that is used in OptionValidator to validate basic characters  */
166         for (final char c : OptionValidator.ADDITIONAL_LONG_CHARS) {
167             assertFalse(Character.isJavaIdentifierPart(c), () -> String.format("'%s' should not be in 'ADDITIONAL_LONG_CHARS", c));
168         }
169         for (final char c : OptionValidator.ADDITIONAL_OPTION_CHARS) {
170             assertFalse(Character.isJavaIdentifierPart(c), () -> String.format("'%s' should not be in 'ADDITIONAL_OPTION_CHARS", c));
171         }
172     }
173 
174     @ParameterizedTest(name = "{2}")
175     @MethodSource("optionParameters")
176     public void validateTest(final String str, final boolean expected, final String name) {
177         if (expected) {
178             assertEquals(str, OptionValidator.validate(str));
179         } else {
180             assertThrows(IllegalArgumentException.class, () -> OptionValidator.validate(str));
181         }
182     }
183 }