001/* 002 * Units of Measurement TCK 003 * Copyright © 2005-2023, Jean-Marie Dautelle, Werner Keil, Otavio Santana. 004 * 005 * All rights reserved. 006 * 007 * Redistribution and use in source and binary forms, with or without modification, 008 * are permitted provided that the following conditions are met: 009 * 010 * 1. Redistributions of source code must retain the above copyright notice, 011 * this list of conditions and the following disclaimer. 012 * 013 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions 014 * and the following disclaimer in the documentation and/or other materials provided with the distribution. 015 * 016 * 3. Neither the name of JSR-385 nor the names of its contributors may be used to endorse or promote products 017 * derived from this software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 020 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 021 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 022 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 023 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 026 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 028 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package tech.units.tck.util; 031 032import static java.lang.reflect.Modifier.PUBLIC; 033import static org.reflections.ReflectionUtils.getAllMethods; 034import static org.reflections.ReflectionUtils.withModifier; 035import static org.reflections.ReflectionUtils.withName; 036import static org.reflections.ReflectionUtils.withParametersCount; 037 038import static org.testng.Assert.assertEquals; 039import static org.testng.Assert.assertNotNull; 040import static org.testng.Assert.assertTrue; 041import static org.testng.Assert.fail; 042 043import java.io.ByteArrayOutputStream; 044import java.io.ObjectOutputStream; 045import java.io.Serializable; 046import java.lang.reflect.InvocationTargetException; 047import java.lang.reflect.Method; 048import java.lang.reflect.Modifier; 049import java.util.Arrays; 050import java.util.Collections; 051import java.util.List; 052import java.util.Random; 053import java.util.Set; 054 055import jakarta.inject.Singleton; 056import tech.units.tck.TCKValidationException; 057 058import javax.measure.spi.*; 059 060/** 061 * Test utilities used in the JSR 385 TCK. 062 * 063 * @author <a href="mailto:[email protected]">Werner Keil</a> 064 * @version 2.7, October 4, 2023 065 * @since 1.0 066 */ 067@Singleton 068public class TestUtils { 069 070 /** 071 * Name of the system property to pass the desired profile 072 */ 073 public static final String SYS_PROPERTY_PROFILE = "tech.units.tck.profile"; 074 075 /** 076 * Name of the system property to override the default output directory 077 */ 078 public static final String SYS_PROPERTY_OUTPUT_DIR = "tech.units.tck.outputDir"; 079 080 /** 081 * Name of the system property to override the default report file 082 */ 083 public static final String SYS_PROPERTY_REPORT_FILE = "tech.units.tck.reportFile"; 084 085 /** 086 * Name of the system property to set the <code>verbose</code> flag 087 */ 088 public static final String SYS_PROPERTY_VERBOSE = "tech.units.tck.verbose"; 089 090 /** 091 * Number of built-in API prefix types 092 */ 093 public static final int NUM_OF_PREFIX_TYPES = 2; 094 095 /** 096 * Number of binary prefixes 097 */ 098 public static final int NUM_OF_BINARY_PREFIXES = 8; 099 100 /** 101 * Number of metric (SI) prefixes 102 */ 103 public static final int NUM_OF_METRIC_PREFIXES = 24; 104 105 /** 106 * Global message for missing TCK Configuration 107 */ 108 public static final String MSG_NO_TCK_CONFIG = "TCK Configuration not available."; 109 110 private static final StringBuilder warnings = new StringBuilder(); 111 112 /** 113 * This class should not be instantiated 114 */ 115 private TestUtils() { 116 } 117 118 static Number createNumberWithPrecision(QuantityFactory<?> f, int precision) { 119 if (precision == 0) { 120 precision = new Random().nextInt(100); 121 } 122 StringBuilder b = new StringBuilder(precision + 1); 123 for (int i = 0; i < precision; i++) { 124 b.append(String.valueOf(i % 10)); 125 } 126 return Double.valueOf(b.toString()); 127 } 128 129 static Number createNumberWithScale(QuantityFactory<?> f, int scale) { 130 StringBuilder b = new StringBuilder(scale + 2); 131 b.append("9."); 132 for (int i = 0; i < scale; i++) { 133 b.append(String.valueOf(i % 10)); 134 } 135 return Double.valueOf(b.toString()); 136 } 137 138 /** 139 * Tests the given object being {@link Serializable}. 140 * 141 * @param section 142 * the section of the spec under test 143 * @param type 144 * the type to be checked. 145 * @throws TCKValidationException 146 * if the test fails. 147 * 148 */ 149 public static void testSerializable(String section, Class<?> type) { 150 if (!Serializable.class.isAssignableFrom(type)) { 151 throw new TCKValidationException(section + ": Class must be serializable: " + type.getName()); 152 } 153 } 154 155 /** 156 * Tests the given object being (effectively) serializable by serializing it. 157 * 158 * @param section 159 * the section of the spec under test 160 * @param o 161 * the object to be checked. 162 * @throws TCKValidationException 163 * if test fails. 164 */ 165 public static void testSerializable(String section, Object o) { 166 if (!Serializable.class.isAssignableFrom(o.getClass())) { 167 throw new TCKValidationException(section + ": Class must be serializable: " + o.getClass().getName()); 168 } 169 try (ObjectOutputStream oos = new ObjectOutputStream(new ByteArrayOutputStream())) { 170 oos.writeObject(o); 171 } catch (Exception e) { 172 throw new TCKValidationException("Class should be serializable, but serialization failed: " + o.getClass().getName(), e); 173 } 174 } 175 176 /** 177 * Test for serializable (optional recommendation), writes a warning if not given. 178 * 179 * @param section 180 * the section of the spec under test 181 * @param type 182 * the type to be checked. 183 * @return true, if the type is probably serializable. 184 */ 185 public static boolean testSerializableOpt(String section, @SuppressWarnings("rawtypes") Class type) { 186 try { 187 testSerializable(section, type); 188 return true; 189 } catch (Exception e) { 190 warnings.append(section).append(": Recommendation failed: Class should be serializable: ").append(type.getName()).append(", details: ") 191 .append(e.getMessage()).append("\n"); 192 return false; 193 } 194 } 195 196 /** 197 * Test for serializable (optional recommendation), writes a warning if not given. 198 * 199 * @param section 200 * the section of the spec under test 201 * @param instance 202 * the object to be checked. 203 * @return true, if the instance is probably serializable. 204 */ 205 public static boolean testSerializableOpt(String section, Object instance) { 206 try { 207 testSerializable(section, instance); 208 return true; 209 } catch (Exception e) { 210 warnings.append(section).append(": Recommendation failed: Class is serializable, but serialization failed: ") 211 .append(instance.getClass().getName()).append("\n"); 212 return false; 213 } 214 } 215 216 /** 217 * Tests the given class implements a given interface. 218 * 219 * @param section 220 * the section of the spec under test 221 * @param type 222 * the type to be checked. 223 * @param iface 224 * the interface to be checked for. 225 * Triggers Assert#fail 226 * if test fails. 227 */ 228 public static void testImplementsInterface(String section, Class<?> type, Class<?> iface) { 229 for (Class<?> ifa : type.getInterfaces()) { 230 if (ifa.equals(iface)) { 231 return; 232 } 233 } 234 fail(section + ": Class must implement " + iface.getName() + ", but does not: " + type.getName()); 235 } 236 237 /** 238 * Tests if the given type is {@link Comparable}. 239 * 240 * @param section 241 * the section of the spec under test 242 * @param type 243 * the type to be checked. 244 * @throws TCKValidationException 245 * if the test fails. 246 */ 247 public static void testComparable(String section, Class<?> type) { 248 if (!Comparable.class.isAssignableFrom(type)) { 249 throw new TCKValidationException(section + ": Class must be comparable: " + type.getName()); 250 } 251 } 252 253 /** 254 * 255 * @param section the section of the specification 256 * @param type the type to be checked. 257 * @param returnType the expected return type 258 * @param name the name of the method 259 * @param paramTypes the types of parameters if available 260 */ 261 public static void testHasPublicMethod(String section, Class<?> type, Class<?> returnType, String name, Class<?>... paramTypes) { 262 Class<?> current = type; 263 while (current != null) { 264 for (Method m : current.getDeclaredMethods()) { 265 if (returnType.equals(m.getReturnType()) && m.getName().equals(name) && ((m.getModifiers() & PUBLIC) != 0) 266 && Arrays.equals(m.getParameterTypes(), paramTypes)) { 267 return; 268 } 269 } 270 current = current.getSuperclass(); 271 } 272 throw new TCKValidationException(section + ": Class must implement method " + name + '(' + Arrays.toString(paramTypes) + "): " 273 + returnType.getName() + ", but does not: " + type.getName()); 274 } 275 276 @SuppressWarnings("rawtypes") 277 private static final List<Class> PRIMITIVE_CLASSES = Collections 278 .unmodifiableList(Arrays.asList(new Class[] { Object.class, Number.class, Enum.class })); 279 280 /** 281 * 282 * @param section the section of the specification 283 * @param type the data type 284 * @param trySuperclassFirst if tht super class if available should be tested first 285 * @param returnType the expected return type 286 * @param name the name of the method 287 * @param paramTypes types of parameters 288 */ 289 public static void testHasPublicMethod(String section, Class<?> type, boolean trySuperclassFirst, Class<?> returnType, String name, 290 Class<?>... paramTypes) { 291 if (trySuperclassFirst && type.getSuperclass() != null) { 292 if (PRIMITIVE_CLASSES.contains(type.getSuperclass())) { 293 testHasPublicMethod(section, type, returnType, name, paramTypes); 294 } else { 295 testHasPublicMethod(section, type.getSuperclass(), returnType, name, paramTypes); 296 } 297 } else { 298 testHasPublicMethod(section, type, returnType, name, paramTypes); 299 } 300 } 301 302 /** 303 * Tests if the given type has a public method with the given signature. 304 * 305 * @param section 306 * the section of the spec under test 307 * @param type 308 * the type to be checked. 309 * @param name 310 * the method name 311 * @param hasParameters 312 * the method has parameters. 313 * @throws TCKValidationException 314 * if test fails. 315 */ 316 @SuppressWarnings({ "unchecked" }) 317 public static void testHasPublicMethod(String section, Class<?> type, String name, boolean hasParameters) { 318 Set<Method> getters; 319 if (hasParameters) { 320 getters = getAllMethods(type, withModifier(PUBLIC), withName(name)); 321 } else { 322 getters = getAllMethods(type, withModifier(PUBLIC), withName(name), withParametersCount(0)); 323 } 324 assertNotNull(getters); 325 assertTrue(getters.size() >= 1); // interface plus at least one implementation 326 } 327 328 /** 329 * @param section the section of the specification 330 * @param type the data type 331 * @param name the name of the method 332 */ 333 public static void testHasPublicMethod(String section, Class<?> type, String name) { 334 testHasPublicMethod(section, type, name, false); 335 } 336 337 /** 338 * Tests if the given type has a public static method with the given signature. 339 * 340 * @param section 341 * the section of the spec under test 342 * @param type 343 * the type to be checked. 344 * @param returnType 345 * the method return type. 346 * @param name 347 * the method name 348 * @param paramTypes 349 * the parameter types. 350 * @throws TCKValidationException 351 * if test fails. 352 */ 353 @SuppressWarnings("rawtypes") 354 static void testHasPublicStaticMethod(String section, Class type, Class returnType, String name, Class... paramTypes) { 355 Class current = type; 356 while (current != null) { 357 for (Method m : current.getDeclaredMethods()) { 358 if (returnType.equals(returnType) && m.getName().equals(name) && ((m.getModifiers() & PUBLIC) != 0) 359 && ((m.getModifiers() & Modifier.STATIC) != 0) && Arrays.equals(m.getParameterTypes(), paramTypes)) { 360 return; 361 } 362 } 363 current = current.getSuperclass(); 364 } 365 throw new TCKValidationException(section + ": Class must implement method " + name + '(' + Arrays.toString(paramTypes) + "): " 366 + returnType.getName() + ", but does not: " + type.getName()); 367 } 368 369 /** 370 * Tests if the given type has not a public method with the given signature. 371 * 372 * @param section 373 * the section of the spec under test 374 * @param type 375 * the type to be checked. 376 * @param returnType 377 * the method return type. 378 * @param name 379 * the method name 380 * @param paramTypes 381 * the parameter types. 382 * @throws TCKValidationException 383 * if test fails. 384 */ 385 @SuppressWarnings("rawtypes") 386 public static void testHasNotPublicMethod(String section, Class<?> type, Class<?> returnType, String name, Class<?>... paramTypes) { 387 Class current = type; 388 while (current != null) { 389 for (Method m : current.getDeclaredMethods()) { 390 if (returnType.equals(returnType) && m.getName().equals(name) && Arrays.equals(m.getParameterTypes(), paramTypes)) { 391 throw new TCKValidationException(section + ": Class must NOT implement method " + name + '(' + Arrays.toString(paramTypes) + "): " 392 + returnType.getName() + ", but does: " + type.getName()); 393 } 394 } 395 current = current.getSuperclass(); 396 } 397 } 398 399 /** 400 * Checks the returned value, when calling a given method. 401 * 402 * @param section 403 * the section of the spec under test 404 * @param value 405 * the expected value 406 * @param methodName 407 * the target method name 408 * @param instance 409 * the instance to call 410 * @throws NoSuchMethodException if no method with the given name exists. 411 * @throws SecurityException if a security problem occurs. 412 * @throws IllegalAccessException if the method may not be called, e.g. due to security constraints. 413 * @throws IllegalArgumentException if a wrong or inappropriate argument was provided. 414 * @throws InvocationTargetException if an exception thrown by an invoked method or constructor. 415 * @throws TCKValidationException 416 * if test fails. 417 */ 418 public static void assertValue(String section, Object value, String methodName, Object instance) 419 throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { 420 final Method m = instance.getClass().getDeclaredMethod(methodName); 421 assertEquals(m.invoke(instance), value, section + ": " + m.getName() + '(' + instance + ") returned invalid value:"); 422 } 423 424 static boolean testHasPublicStaticMethodOpt(String section, @SuppressWarnings("rawtypes") Class type, @SuppressWarnings("rawtypes") Class returnType, String methodName, @SuppressWarnings("rawtypes") Class... paramTypes) { 425 try { 426 testHasPublicStaticMethod(section, type, returnType, methodName, paramTypes); 427 return true; 428 } catch (Exception e) { 429 warnings.append(section).append(": Recommendation failed: Missing method [public static ").append(methodName).append('(') 430 .append(Arrays.toString(paramTypes)).append("):").append(returnType.getName()).append("] on: ").append(type.getName()) 431 .append("\n"); 432 return false; 433 } 434 } 435 436 /** 437 * Tests the given class being immutable. 438 * 439 * @param section 440 * the section of the spec under test 441 * @param type 442 * the type to be checked. 443 * @throws TCKValidationException 444 * if test fails. 445 * @deprecated This was never used by the TCK, as immutability is highly recommended, but not enforced. MutabilityDetector is also incompatible with the Java Platform Module System and not actively developed in recent years. 446 */ 447 public static void testImmutable(String section, Class<?> type) { 448// try { 449// MutabilityAssert.assertInstancesOf(type, MutabilityMatchers.areImmutable(), 450// AllowedReason.provided(Dimension.class, Quantity.class, Unit.class, UnitConverter.class).areAlsoImmutable(), 451// AllowedReason.allowingForSubclassing(), AllowedReason.allowingNonFinalFields()); 452// } catch (Exception e) { 453// throw new TCKValidationException(section + ": Class is not immutable: " + type.getName(), e); 454// } 455 } 456 457 /** 458 * Test for immutability (optional recommendation), writes a warning if not given. 459 * 460 * @param section 461 * the section of the spec under test 462 * @param type 463 * the type to be checked. 464 * @return true, if the instance is probably immutable. 465 * 466 * @deprecated This was never used by the TCK, as immutability is highly recommended, but not enforced. MutabilityDetector is also incompatible with the Java Platform Module System and not actively developed in recent years. 467 */ 468 public static boolean testImmutableOpt(String section, @SuppressWarnings("rawtypes") Class type) { 469 try { 470 testImmutable(section, type); 471 return true; 472 } catch (Exception e) { 473 warnings.append(section).append(": Recommendation failed: Class should be immutable: ").append(type.getName()).append(", details: ") 474 .append(e.getMessage()).append("\n"); 475 return false; 476 } 477 } 478 479 static void resetWarnings() { 480 warnings.setLength(0); 481 } 482 483 static String getWarnings() { 484 return warnings.toString(); 485 } 486}