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  package org.apache.juneau.junit.bct;
18  
19  import static org.apache.juneau.junit.bct.BctAssertions.*;
20  import static org.apache.juneau.junit.bct.Utils.*;
21  import static org.junit.jupiter.api.Assertions.*;
22  
23  import java.util.*;
24  import java.util.regex.*;
25  
26  import org.apache.juneau.*;
27  import org.junit.jupiter.api.*;
28  
29  /**
30   * Comprehensive unit tests for the {@link Utils} utility class.
31   *
32   * <p>This test class validates all utility methods for correct behavior, edge cases,
33   * and error conditions. Tests are organized by functional groups matching the
34   * utility method categories.
35   */
36  @DisplayName("Utility Methods")
37  class Utils_Test extends TestBase {
38  
39  	// ====================================================================================================
40  	// Array Conversion Tests
41  	// ====================================================================================================
42  
43  	@Test
44  	@DisplayName("arrayToList() - Basic array conversion")
45  	void a01_arrayToListBasicConversion() {
46  		// Test with integer array
47  		int[] intArray = {1, 2, 3, 4, 5};
48  		var result = arrayToList(intArray);
49  		assertEquals(5, result.size());
50  		assertEquals(1, result.get(0));
51  		assertEquals(2, result.get(1));
52  		assertEquals(3, result.get(2));
53  		assertEquals(4, result.get(3));
54  		assertEquals(5, result.get(4));
55  
56  		// Test with string array
57  		String[] stringArray = {"hello", "world", "test"};
58  		result = arrayToList(stringArray);
59  		assertEquals(3, result.size());
60  		assertEquals("hello", result.get(0));
61  		assertEquals("world", result.get(1));
62  		assertEquals("test", result.get(2));
63  	}
64  
65  	@Test
66  	@DisplayName("arrayToList() - Empty and single element arrays")
67  	void a02_arrayToListEdgeCases() {
68  		// Empty array
69  		int[] emptyArray = {};
70  		var result = arrayToList(emptyArray);
71  		assertEmpty(result);
72  
73  		// Single element
74  		String[] singleElement = {"single"};
75  		result = arrayToList(singleElement);
76  		assertList(result, "single");
77  	}
78  
79  	@Test
80  	@DisplayName("arrayToList() - Null values in array")
81  	void a03_arrayToListWithNulls() {
82  		String[] arrayWithNulls = {"first", null, "third", null};
83  		var result = arrayToList(arrayWithNulls);
84  		assertEquals(4, result.size());
85  		assertEquals("first", result.get(0));
86  		assertNull(result.get(1));
87  		assertEquals("third", result.get(2));
88  		assertNull(result.get(3));
89  	}
90  
91  	@Test
92  	@DisplayName("arrayToList() - Different primitive types")
93  	void a04_arrayToListPrimitiveTypes() {
94  		// Boolean array
95  		boolean[] boolArray = {true, false, true};
96  		var result = arrayToList(boolArray);
97  		assertList(result, "true","false","true");
98  
99  		// Double array
100 		double[] doubleArray = {1.5, 2.7, 3.14};
101 		result = arrayToList(doubleArray);
102 		assertList(result, "1.5","2.7","3.14");
103 	}
104 
105 	// ====================================================================================================
106 	// Argument Validation Tests
107 	// ====================================================================================================
108 
109 	@Test
110 	@DisplayName("assertArg() - Valid expressions")
111 	void b01_assertArgValidExpressions() {
112 		// Should not throw
113 		assertDoesNotThrow(() -> assertArg(true, "Should not fail"));
114 		assertDoesNotThrow(() -> assertArg(5 > 3, "Math should work"));
115 		assertDoesNotThrow(() -> assertArg("test".length() == 4, "Length check"));
116 	}
117 
118 	@Test
119 	@DisplayName("assertArg() - Invalid expressions")
120 	void b02_assertArgInvalidExpressions() {
121 		// Simple false expression
122 		var e = assertThrows(IllegalArgumentException.class, () -> assertArg(false, "This should fail"));
123 		assertEquals("This should fail", e.getMessage());
124 
125 		// False expression with parameters
126 		e = assertThrows(IllegalArgumentException.class, () -> assertArg(5 < 3, "Value {0} should be greater than {1}", 5, 3));
127 		assertEquals("Value 5 should be greater than 3", e.getMessage());
128 	}
129 
130 	@Test
131 	@DisplayName("assertArg() - Message formatting")
132 	void b03_assertArgMessageFormatting() {
133 		var e = assertThrows(IllegalArgumentException.class, () -> assertArg(false, "User {0} has invalid age {1}", "Alice", -5));
134 		assertEquals("User Alice has invalid age -5", e.getMessage());
135 	}
136 
137 	@Test
138 	@DisplayName("assertArgNotNull() - Valid arguments")
139 	void b04_assertArgNotNullValid() {
140 		var result = assertArgNotNull("testParam", "validValue");
141 		assertEquals("validValue", result);
142 
143 		var intResult = assertArgNotNull("number", 42);
144 		assertEquals(Integer.valueOf(42), intResult);
145 
146 		var list = new ArrayList<>();
147 		var listResult = assertArgNotNull("list", list);
148 		assertSame(list, listResult);
149 	}
150 
151 	@Test
152 	@DisplayName("assertArgNotNull() - Null arguments")
153 	void b05_assertArgNotNullInvalid() {
154 		var e = assertThrows(IllegalArgumentException.class, () -> assertArgNotNull("username", null));
155 		assertEquals("Argument 'username' cannot be null.", e.getMessage());
156 
157 		e = assertThrows(IllegalArgumentException.class, () -> assertArgNotNull("data", null));
158 		assertEquals("Argument 'data' cannot be null.", e.getMessage());
159 	}
160 
161 	// ====================================================================================================
162 	// Assertion Error Creation Tests
163 	// ====================================================================================================
164 
165 	@Test
166 	@DisplayName("assertEqualsFailed() - Basic error creation")
167 	void c01_assertEqualsFailedBasic() {
168 		var error = assertEqualsFailed("expected", "actual", null);
169 		assertContains("expected: <expected>", error.getMessage());
170 		assertContains("but was: <actual>", error.getMessage());
171 		assertEquals("expected", error.getExpected().getValue());
172 		assertEquals("actual", error.getActual().getValue());
173 	}
174 
175 	@Test
176 	@DisplayName("assertEqualsFailed() - With custom message")
177 	void c02_assertEqualsFailedWithMessage() {
178 		var msgSupplier = fs("Custom context message");
179 		var error = assertEqualsFailed(100, 200, msgSupplier);
180 		assertContains("Custom context message", error.getMessage());
181 	}
182 
183 	@Test
184 	@DisplayName("assertEqualsFailed() - Null values")
185 	void c03_assertEqualsFailedNullValues() {
186 		var error = assertEqualsFailed(null, "actual", null);
187 		assertContains("expected: <null>", error.getMessage());
188 		assertContains("but was: <actual>", error.getMessage());
189 
190 		error = assertEqualsFailed("expected", null, null);
191 		assertContains("expected: <expected>", error.getMessage());
192 		assertContains("but was: <null>", error.getMessage());
193 	}
194 
195 	// ====================================================================================================
196 	// Equality Testing Tests
197 	// ====================================================================================================
198 
199 	@Test
200 	@DisplayName("eq() - Basic equality with Objects.equals()")
201 	void d01_eqBasicEquality() {
202 		assertTrue(eq("hello", "hello"));
203 		assertTrue(eq(null, null));
204 		assertTrue(eq(42, 42));
205 
206 		assertFalse(eq("hello", "world"));
207 		assertFalse(eq(null, "test"));
208 		assertFalse(eq("test", null));
209 		assertFalse(eq(42, 43));
210 	}
211 
212 	@Test
213 	@DisplayName("eq() - Custom predicate equality")
214 	void d02_eqCustomPredicate() {
215 		// Case-insensitive string comparison
216 		assertTrue(eq("HELLO", "hello", (s1, s2) -> s1.equalsIgnoreCase(s2)));
217 		assertFalse(eq("HELLO", "world", (s1, s2) -> s1.equalsIgnoreCase(s2)));
218 
219 		// Custom object comparison by ID
220 		var bean1 = new TestBean("Alice", 25, true);
221 		var bean2 = new TestBean("Bob", 25, false);
222 		assertTrue(eq(bean1, bean2, (b1, b2) -> b1.getAge() == b2.getAge()));
223 		assertFalse(eq(bean1, bean2, (b1, b2) -> b1.getName().equals(b2.getName())));
224 	}
225 
226 	@Test
227 	@DisplayName("eq() - Null handling with custom predicate")
228 	void d03_eqCustomPredicateNulls() {
229 		// Both null
230 		assertTrue(eq(null, null, (s1, s2) -> s1.equals(s2)));
231 
232 		// One null
233 		assertFalse(eq(null, "test", (s1, s2) -> s1.equals(s2)));
234 		assertFalse(eq("test", null, (s1, s2) -> s1.equals(s2)));
235 	}
236 
237 	@Test
238 	@DisplayName("eq() - Reference equality optimization")
239 	void d04_eqReferenceEquality() {
240 		var same = "test";
241 		assertTrue(eq(same, same, (s1, s2) -> { throw new RuntimeException("Should not be called"); }));
242 	}
243 
244 	@Test
245 	@DisplayName("ne() - Negation of equality")
246 	void d05_neNegation() {
247 		assertTrue(ne("hello", "world"));
248 		assertTrue(ne(null, "test"));
249 		assertTrue(ne("test", null));
250 
251 		assertFalse(ne("hello", "hello"));
252 		assertFalse(ne(null, null));
253 		assertFalse(ne(42, 42));
254 	}
255 
256 	// ====================================================================================================
257 	// String Escaping Tests
258 	// ====================================================================================================
259 
260 	@Test
261 	@DisplayName("escapeForJava() - Basic escape sequences")
262 	void e01_escapeForJavaBasicEscapes() {
263 		assertEquals("\\\"", escapeForJava("\""));
264 		assertEquals("\\\\", escapeForJava("\\"));
265 		assertEquals("\\n", escapeForJava("\n"));
266 		assertEquals("\\r", escapeForJava("\r"));
267 		assertEquals("\\t", escapeForJava("\t"));
268 		assertEquals("\\f", escapeForJava("\f"));
269 		assertEquals("\\b", escapeForJava("\b"));
270 	}
271 
272 	@Test
273 	@DisplayName("escapeForJava() - Combined sequences")
274 	void e02_escapeForJavaCombined() {
275 		var input = "Hello\nWorld\"Test\"";
276 		var expected = "Hello\\nWorld\\\"Test\\\"";
277 		assertEquals(expected, escapeForJava(input));
278 
279 		input = "Line1\r\nLine2\tTabbed";
280 		expected = "Line1\\r\\nLine2\\tTabbed";
281 		assertEquals(expected, escapeForJava(input));
282 	}
283 
284 	@Test
285 	@DisplayName("escapeForJava() - Unicode escapes")
286 	void e03_escapeForJavaUnicode() {
287 		// Control characters
288 		assertEquals("\\u0001", escapeForJava("\u0001"));
289 		assertEquals("\\u001f", escapeForJava("\u001f"));
290 
291 		// Non-ASCII characters
292 		assertEquals("\\u007f", escapeForJava("\u007f"));
293 		assertEquals("\\u0080", escapeForJava("\u0080"));
294 		assertEquals("\\u00ff", escapeForJava("\u00ff"));
295 	}
296 
297 	@Test
298 	@DisplayName("escapeForJava() - Normal characters unchanged")
299 	void e04_escapeForJavaNormalChars() {
300 		var normal = "ABCabc123!@#$%^&*()_+-=[]{}|;':,.<>?";
301 		assertEquals(normal, escapeForJava(normal));
302 	}
303 
304 	@Test
305 	@DisplayName("escapeForJava() - Empty string")
306 	void e05_escapeForJavaEmpty() {
307 		assertEquals("", escapeForJava(""));
308 	}
309 
310 	// ====================================================================================================
311 	// Message Formatting Tests
312 	// ====================================================================================================
313 
314 	@Test
315 	@DisplayName("f() - No parameters")
316 	void f01_fNoParameters() {
317 		assertEquals("Simple message", f("Simple message"));
318 		assertEquals("", f(""));
319 	}
320 
321 	@Test
322 	@DisplayName("f() - With parameters")
323 	void f02_fWithParameters() {
324 		assertEquals("User Alice has 5 items", f("User {0} has {1} items", "Alice", 5));
325 		assertEquals("Value: 42", f("Value: {0}", 42));
326 		assertEquals("a, b, c", f("{0}, {1}, {2}", "a", "b", "c"));
327 	}
328 
329 	@Test
330 	@DisplayName("f() - Complex parameter types")
331 	void f03_fComplexParameters() {
332 		// Test with Boolean
333 		assertString(f("Flag: {0}", true), "Flag: true");
334 		assertString(f("Flag: {0}", false), "Flag: false");
335 
336 		// Test with List
337 		var list = Arrays.asList("a", "b", "c");
338 		assertString(f("List: {0}", list), "List: [a, b, c]");
339 
340 		// Test with mixed types
341 		assertString(f("User {0} (age {1}) is active: {2}", "Alice", 25, true), "User Alice (age 25) is active: true");
342 	}
343 
344 	@Test
345 	@DisplayName("f() - Edge cases and special characters")
346 	void f04_fEdgeCases() {
347 		// Test with special characters (avoid braces in parameters for MessageFormat compatibility)
348 		assertEquals("Pattern: .*items=\\[a,b\\].*", f("Pattern: {0}", ".*items=\\[a,b\\].*"));
349 		assertEquals("Simple: no braces here", f("Simple: {0}", "no braces here"));
350 		assertEquals("Mixed: string with data and 42", f("Mixed: {0} and {1}", "string with data", 42));
351 
352 		// Test with non-string arguments
353 		assertEquals("Number: 42", f("Number: {0}", 42));
354 		assertEquals("Boolean: true", f("Boolean: {0}", true));
355 
356 		// Test that single quotes in input are properly handled (need to be doubled in MessageFormat)
357 		assertEquals("Text with 'single quotes'", f("Text with {0}", "'single quotes'"));
358 	}
359 
360 	@Test
361 	@DisplayName("fs() - Supplier creation")
362 	void f05_fsSupplierCreation() {
363 		var supplier = fs("User {0} has {1} items", "Bob", 3);
364 		assertEquals("User Bob has 3 items", supplier.get());
365 
366 		// Multiple calls should produce same result
367 		assertEquals("User Bob has 3 items", supplier.get());
368 	}
369 
370 	@Test
371 	@DisplayName("fs() - No parameters supplier")
372 	void f06_fsNoParameters() {
373 		assertEquals("Static message", fs("Static message").get());
374 	}
375 
376 	// ====================================================================================================
377 	// Pattern Matching Tests
378 	// ====================================================================================================
379 
380 	@Test
381 	@DisplayName("getGlobMatchPattern() - Basic wildcard patterns")
382 	void g01_getGlobMatchPatternBasicWildcards() {
383 		var pattern = getGlobMatchPattern("user_*_temp");
384 		assertTrue(pattern.matcher("user_alice_temp").matches());
385 		assertTrue(pattern.matcher("user_bob_temp").matches());
386 		assertTrue(pattern.matcher("user__temp").matches());  // Empty match
387 		assertFalse(pattern.matcher("admin_alice_temp").matches());
388 		assertFalse(pattern.matcher("user_alice_data").matches());
389 	}
390 
391 	@Test
392 	@DisplayName("getGlobMatchPattern() - Question mark patterns")
393 	void g02_getGlobMatchPatternQuestionMark() {
394 		var pattern = getGlobMatchPattern("file?.txt");
395 		assertTrue(pattern.matcher("file1.txt").matches());
396 		assertTrue(pattern.matcher("fileA.txt").matches());
397 		assertTrue(pattern.matcher("file_.txt").matches());
398 		assertFalse(pattern.matcher("file.txt").matches());    // Missing character
399 		assertFalse(pattern.matcher("file12.txt").matches());  // Too many characters
400 	}
401 
402 	@Test
403 	@DisplayName("getGlobMatchPattern() - Combined wildcards")
404 	void g03_getGlobMatchPatternCombined() {
405 		var pattern = getGlobMatchPattern("test_*.?");
406 		assertTrue(pattern.matcher("test_data.1").matches());
407 		assertTrue(pattern.matcher("test_long_filename.x").matches());
408 		assertTrue(pattern.matcher("test_.a").matches());
409 		assertFalse(pattern.matcher("test_data").matches());   // Missing single char
410 		assertFalse(pattern.matcher("test_data.ab").matches()); // Too many chars at end
411 	}
412 
413 	@Test
414 	@DisplayName("getGlobMatchPattern() - Special regex characters")
415 	void g04_getGlobMatchPatternSpecialChars() {
416 		// Test that regex special characters are properly escaped
417 		var pattern = getGlobMatchPattern("file[1].txt");
418 		assertTrue(pattern.matcher("file[1].txt").matches());
419 		assertFalse(pattern.matcher("file1.txt").matches());   // Should not match as character class
420 
421 		pattern = getGlobMatchPattern("amount$100");
422 		assertTrue(pattern.matcher("amount$100").matches());
423 		assertFalse(pattern.matcher("amount100").matches());   // Should require the $ character
424 	}
425 
426 	@Test
427 	@DisplayName("getGlobMatchPattern() - With flags")
428 	void g05_getGlobMatchPatternWithFlags() {
429 		var pattern = getGlobMatchPattern("USER_*", Pattern.CASE_INSENSITIVE);
430 		assertTrue(pattern.matcher("user_alice").matches());
431 		assertTrue(pattern.matcher("USER_BOB").matches());
432 		assertTrue(pattern.matcher("User_Charlie").matches());
433 	}
434 
435 	@Test
436 	@DisplayName("getGlobMatchPattern() - Null input")
437 	void g06_getGlobMatchPatternNull() {
438 		assertNull(getGlobMatchPattern(null));
439 		assertNull(getGlobMatchPattern(null, Pattern.CASE_INSENSITIVE));
440 	}
441 
442 	@Test
443 	@DisplayName("getGlobMatchPattern() - Empty and edge cases")
444 	void g07_getGlobMatchPatternEdgeCases() {
445 		// Empty string
446 		var pattern = getGlobMatchPattern("");
447 		assertTrue(pattern.matcher("").matches());
448 		assertFalse(pattern.matcher("a").matches());
449 
450 		// Only wildcards
451 		pattern = getGlobMatchPattern("*");
452 		assertTrue(pattern.matcher("").matches());
453 		assertTrue(pattern.matcher("anything").matches());
454 
455 		pattern = getGlobMatchPattern("?");
456 		assertTrue(pattern.matcher("a").matches());
457 		assertFalse(pattern.matcher("").matches());
458 		assertFalse(pattern.matcher("ab").matches());
459 	}
460 
461 	// ====================================================================================================
462 	// Safe Execution Tests
463 	// ====================================================================================================
464 
465 	@Test
466 	@DisplayName("safe() - Successful execution")
467 	void h01_safeSuccessfulExecution() {
468 		assertEquals("success", safe(() -> "success"));
469 		assertEquals(Integer.valueOf(42), safe(() -> 42));
470 	}
471 
472 	@Test
473 	@DisplayName("safe() - Runtime exception passthrough")
474 	void h02_safeRuntimeExceptionPassthrough() {
475 		var original = new RuntimeException("Test runtime exception");
476 		var thrown = assertThrows(RuntimeException.class, () -> safe(() -> { throw original; }));
477 		assertSame(original, thrown);
478 
479 		var argException = new IllegalArgumentException("Invalid arg");
480 		var thrownArg = assertThrows(IllegalArgumentException.class, () -> safe(() -> { throw argException; }));
481 		assertSame(argException, thrownArg);
482 	}
483 
484 	@Test
485 	@DisplayName("safe() - Checked exception wrapping")
486 	void h03_safeCheckedExceptionWrapping() {
487 		var checkedException = new Exception("Checked exception");
488 		var thrown = assertThrows(RuntimeException.class, () -> safe(() -> { throw checkedException; }));
489 		assertSame(checkedException, thrown.getCause());
490 		assertEquals(RuntimeException.class, thrown.getClass());
491 	}
492 
493 	// ====================================================================================================
494 	// Type Name Tests
495 	// ====================================================================================================
496 
497 	@Test
498 	@DisplayName("t() - Various object types")
499 	void i01_tVariousTypes() {
500 		assertString(t("hello"), "String");
501 		assertString(t(42), "Integer");
502 		assertString(t(new ArrayList<>()), "ArrayList");
503 		assertString(t(new HashMap<>()), "HashMap");
504 		assertString(t(Pattern.compile("test")), "Pattern");
505 	}
506 
507 	@Test
508 	@DisplayName("t() - Null input")
509 	void i02_tNullInput() {
510 		assertNull(t(null));
511 	}
512 
513 	@Test
514 	@DisplayName("t() - Array types")
515 	void i03_tArrayTypes() {
516 		assertString(t(new String[0]), "String[]");
517 		assertString(t(new int[0]), "int[]");
518 		assertString(t(new Object[0]), "Object[]");
519 	}
520 
521 	// ====================================================================================================
522 	// Tokenization Tests
523 	// ====================================================================================================
524 
525 	@Test
526 	@DisplayName("tokenize() - Basic tokenization")
527 	void j01_tokenizeBasic() {
528 		// This test assumes NestedTokenizer.tokenize() works correctly
529 		// We're just testing the delegation
530 		assertNotNull(tokenize("name,age"));
531 		// The actual behavior depends on NestedTokenizer implementation
532 	}
533 
534 	@Test
535 	@DisplayName("tokenize() - Nested fields")
536 	void j02_tokenizeNested() {
537 		// Test with nested structure
538 		assertNotNull(tokenize("name,address{street,city}"));
539 		// The actual behavior depends on NestedTokenizer implementation
540 	}
541 
542 	// ====================================================================================================
543 	// Test Helper Classes
544 	// ====================================================================================================
545 
546 	/**
547 	 * Simple test bean for testing purposes.
548 	 */
549 	public static class TestBean {
550 		private final String name;
551 		private final int age;
552 		private final boolean active;
553 
554 		public TestBean(String name, int age, boolean active) {
555 			this.name = name;
556 			this.age = age;
557 			this.active = active;
558 		}
559 
560 		public String getName() { return name; }
561 		public int getAge() { return age; }
562 		public boolean isActive() { return active; }
563 
564 		@Override
565 		public boolean equals(Object obj) {
566 			return (obj instanceof TestBean other) && eq(this, other, (x,y) -> eq(x.age, y.age) && eq(x.active, y.active) && eq(x.name, y.name));
567 		}
568 
569 		@Override
570 		public int hashCode() {
571 			return Objects.hash(name, age, active);
572 		}
573 
574 		@Override
575 		public String toString() {
576 			return f("TestBean{name=''{0}'', age=''{1}'', active=''{2}''}", name, age, active);
577 		}
578 	}
579 }