001/* 002 * Units of Measurement TCK 003 * Copyright © 2005-2020, 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.hamcrest.MatcherAssert.assertThat; 034import static org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo; 035import static org.reflections.ReflectionUtils.getAllMethods; 036import static org.reflections.ReflectionUtils.withModifier; 037import static org.reflections.ReflectionUtils.withName; 038import static org.reflections.ReflectionUtils.withParametersCount; 039 040import java.io.ByteArrayOutputStream; 041import java.io.ObjectOutputStream; 042import java.io.Serializable; 043import java.lang.reflect.InvocationTargetException; 044import java.lang.reflect.Method; 045import java.lang.reflect.Modifier; 046import java.util.Arrays; 047import java.util.Collections; 048import java.util.List; 049import java.util.Random; 050import java.util.Set; 051 052import org.mutabilitydetector.unittesting.AllowedReason; 053import org.mutabilitydetector.unittesting.MutabilityAssert; 054import org.mutabilitydetector.unittesting.MutabilityMatchers; 055import org.testng.Assert; 056 057import jakarta.inject.Singleton; 058import tech.units.tck.TCKValidationException; 059 060import javax.measure.*; 061import javax.measure.spi.*; 062 063/** 064 * Test utilities used in the JSR 385 TCK. 065 * 066 * @author <a href="mailto:[email protected]">Werner Keil</a> 067 * @version 2.2, November 15, 2020 068 * @since 1.0 069 */ 070@Singleton 071public class TestUtils { 072 073 /** 074 * Name of the system property to pass the desired profile 075 */ 076 public static final String SYS_PROPERTY_PROFILE = "tech.units.tck.profile"; 077 078 /** 079 * Name of the system property to override the default output directory 080 */ 081 public static final String SYS_PROPERTY_OUTPUT_DIR = "tech.units.tck.outputDir"; 082 083 /** 084 * Name of the system property to override the default report file 085 */ 086 public static final String SYS_PROPERTY_REPORT_FILE = "tech.units.tck.reportFile"; 087 088 /** 089 * Name of the system property to set the <code>verbose</code> flag 090 */ 091 public static final String SYS_PROPERTY_VERBOSE = "tech.units.tck.verbose"; 092 093 private static final StringBuilder warnings = new StringBuilder(); 094 095 /** 096 * This class should not be instantiated 097 */ 098 private TestUtils() { 099 } 100 101 static Number createNumberWithPrecision(QuantityFactory<?> f, int precision) { 102 if (precision == 0) { 103 precision = new Random().nextInt(100); 104 } 105 StringBuilder b = new StringBuilder(precision + 1); 106 for (int i = 0; i < precision; i++) { 107 b.append(String.valueOf(i % 10)); 108 } 109 return new Double(b.toString()); 110 } 111 112 static Number createNumberWithScale(QuantityFactory<?> f, int scale) { 113 StringBuilder b = new StringBuilder(scale + 2); 114 b.append("9."); 115 for (int i = 0; i < scale; i++) { 116 b.append(String.valueOf(i % 10)); 117 } 118 return new Double(b.toString()); 119 } 120 121 /** 122 * Tests the given object being (effectively) serializable by serializing it. 123 * 124 * @param section 125 * the section of the spec under test 126 * @param type 127 * the type to be checked. 128 * @throws TCKValidationException 129 * if the test fails. 130 */ 131 public static void testSerializable(String section, Class<?> type) { 132 if (!Serializable.class.isAssignableFrom(type)) { 133 throw new TCKValidationException(section + ": Class must be serializable: " + type.getName()); 134 } 135 } 136 137 /** 138 * Tests the given class being serializable. 139 * 140 * @param section 141 * the section of the spec under test 142 * @param type 143 * the type to be checked. 144 * @throws TCKValidationException 145 * if test fails. 146 */ 147 public static void testImmutable(String section, Class<?> type) { 148 try { 149 MutabilityAssert.assertInstancesOf(type, MutabilityMatchers.areImmutable(), 150 AllowedReason.provided(Dimension.class, Quantity.class, Unit.class, UnitConverter.class).areAlsoImmutable(), 151 AllowedReason.allowingForSubclassing(), AllowedReason.allowingNonFinalFields()); 152 } catch (Exception e) { 153 throw new TCKValidationException(section + ": Class is not immutable: " + type.getName(), e); 154 } 155 } 156 157 /** 158 * Tests the given object being (effectively) serializable by serializing it. 159 * 160 * @param section 161 * the section of the spec under test 162 * @param o 163 * the object to be checked. 164 * @throws TCKValidationException 165 * if test fails. 166 */ 167 public static void testSerializable(String section, Object o) { 168 if (!(o instanceof Serializable)) { 169 throw new TCKValidationException(section + ": Class must be serializable: " + o.getClass().getName()); 170 } 171 try (ObjectOutputStream oos = new ObjectOutputStream(new ByteArrayOutputStream())) { 172 oos.writeObject(o); 173 } catch (Exception e) { 174 throw new TCKValidationException("Class must be serializable, but serialization failed: " + o.getClass().getName(), e); 175 } 176 } 177 178 /** 179 * Tests the given class implements a given interface. 180 * 181 * @param section 182 * the section of the spec under test 183 * @param type 184 * the type to be checked. 185 * @param iface 186 * the interface to be checked for. 187 * Triggers Assert#fail 188 * if test fails. 189 */ 190 public static void testImplementsInterface(String section, Class<?> type, Class<?> iface) { 191 for (Class<?> ifa : type.getInterfaces()) { 192 if (ifa.equals(iface)) { 193 return; 194 } 195 } 196 Assert.fail(section + ": Class must implement " + iface.getName() + ", but does not: " + type.getName()); 197 } 198 199 /** 200 * Tests if the given type is comparable. 201 * 202 * @param section 203 * the section of the spec under test 204 * @param type 205 * the type to be checked. 206 */ 207 public static void testComparable(String section, Class<?> type) { 208 testImplementsInterface(section, type, Comparable.class); 209 } 210 211 /** 212 * 213 * @param section the section of the specification 214 * @param type the type to be checked. 215 * @param returnType the expected return type 216 * @param name the name of the method 217 * @param paramTypes the types of parameters if available 218 */ 219 public static void testHasPublicMethod(String section, Class<?> type, Class<?> returnType, String name, Class<?>... paramTypes) { 220 Class<?> current = type; 221 while (current != null) { 222 for (Method m : current.getDeclaredMethods()) { 223 if (returnType.equals(returnType) && m.getName().equals(name) && ((m.getModifiers() & PUBLIC) != 0) 224 && Arrays.equals(m.getParameterTypes(), paramTypes)) { 225 return; 226 } 227 } 228 current = current.getSuperclass(); 229 } 230 throw new TCKValidationException(section + ": Class must implement method " + name + '(' + Arrays.toString(paramTypes) + "): " 231 + returnType.getName() + ", but does not: " + type.getName()); 232 } 233 234 @SuppressWarnings("rawtypes") 235 private static final List<Class> PRIMITIVE_CLASSES = Collections 236 .unmodifiableList(Arrays.asList(new Class[] { Object.class, Number.class, Enum.class })); 237 238 /** 239 * 240 * @param section the section of the specification 241 * @param type the data type 242 * @param trySuperclassFirst if tht super class if available should be tested first 243 * @param returnType the expected return type 244 * @param name the name of the method 245 * @param paramTypes types of parameters 246 */ 247 public static void testHasPublicMethod(String section, Class<?> type, boolean trySuperclassFirst, Class<?> returnType, String name, 248 Class<?>... paramTypes) { 249 if (trySuperclassFirst && type.getSuperclass() != null) { 250 if (PRIMITIVE_CLASSES.contains(type.getSuperclass())) { 251 testHasPublicMethod(section, type, returnType, name, paramTypes); 252 } else { 253 testHasPublicMethod(section, type.getSuperclass(), returnType, name, paramTypes); 254 } 255 } else { 256 testHasPublicMethod(section, type, returnType, name, paramTypes); 257 } 258 } 259 260 /** 261 * Tests if the given type has a public method with the given signature. 262 * 263 * @param section 264 * the section of the spec under test 265 * @param type 266 * the type to be checked. 267 * @param name 268 * the method name 269 * @param hasParameters 270 * the method has parameters. 271 * @throws TCKValidationException 272 * if test fails. 273 */ 274 @SuppressWarnings({ "unchecked" }) 275 public static void testHasPublicMethod(String section, Class<?> type, String name, boolean hasParameters) { 276 Set<Method> getters; 277 if (hasParameters) { 278 getters = getAllMethods(type, withModifier(PUBLIC), withName(name)); 279 } else { 280 getters = getAllMethods(type, withModifier(PUBLIC), withName(name), withParametersCount(0)); 281 } 282 assertThat(getters.size(), greaterThanOrEqualTo(1)); // interface plus 283 // at least one implementation 284 } 285 286 /** 287 * @param section the section of the specification 288 * @param type the data type 289 * @param name the name of the method 290 */ 291 public static void testHasPublicMethod(String section, Class<?> type, String name) { 292 testHasPublicMethod(section, type, name, false); 293 } 294 295 /** 296 * Tests if the given type has a public static method with the given signature. 297 * 298 * @param section 299 * the section of the spec under test 300 * @param type 301 * the type to be checked. 302 * @param returnType 303 * the method return type. 304 * @param name 305 * the method name 306 * @param paramTypes 307 * the parameter types. 308 * @throws TCKValidationException 309 * if test fails. 310 */ 311 @SuppressWarnings("rawtypes") 312 static void testHasPublicStaticMethod(String section, Class type, Class returnType, String name, Class... paramTypes) { 313 Class current = type; 314 while (current != null) { 315 for (Method m : current.getDeclaredMethods()) { 316 if (returnType.equals(returnType) && m.getName().equals(name) && ((m.getModifiers() & PUBLIC) != 0) 317 && ((m.getModifiers() & Modifier.STATIC) != 0) && Arrays.equals(m.getParameterTypes(), paramTypes)) { 318 return; 319 } 320 } 321 current = current.getSuperclass(); 322 } 323 throw new TCKValidationException(section + ": Class must implement method " + name + '(' + Arrays.toString(paramTypes) + "): " 324 + returnType.getName() + ", but does not: " + type.getName()); 325 } 326 327 /** 328 * Tests if the given type has not a public method with the given signature. 329 * 330 * @param section 331 * the section of the spec under test 332 * @param type 333 * the type to be checked. 334 * @param returnType 335 * the method return type. 336 * @param name 337 * the method name 338 * @param paramTypes 339 * the parameter types. 340 * @throws TCKValidationException 341 * if test fails. 342 */ 343 @SuppressWarnings("rawtypes") 344 public static void testHasNotPublicMethod(String section, Class<?> type, Class<?> returnType, String name, Class<?>... paramTypes) { 345 Class current = type; 346 while (current != null) { 347 for (Method m : current.getDeclaredMethods()) { 348 if (returnType.equals(returnType) && m.getName().equals(name) && Arrays.equals(m.getParameterTypes(), paramTypes)) { 349 throw new TCKValidationException(section + ": Class must NOT implement method " + name + '(' + Arrays.toString(paramTypes) + "): " 350 + returnType.getName() + ", but does: " + type.getName()); 351 } 352 } 353 current = current.getSuperclass(); 354 } 355 } 356 357 /** 358 * Checks the returned value, when calling a given method. 359 * 360 * @param section 361 * the section of the spec under test 362 * @param value 363 * the expected value 364 * @param methodName 365 * the target method name 366 * @param instance 367 * the instance to call 368 * @throws NoSuchMethodException if no method with the given name exists. 369 * @throws SecurityException if a security problem occurs. 370 * @throws IllegalAccessException if the method may not be called, e.g. due to security constraints. 371 * @throws IllegalArgumentException if a wrong or inappropriate argument was provided. 372 * @throws InvocationTargetException if an exception thrown by an invoked method or constructor. 373 * @throws TCKValidationException 374 * if test fails. 375 */ 376 public static void assertValue(String section, Object value, String methodName, Object instance) 377 throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { 378 Method m = instance.getClass().getDeclaredMethod(methodName); 379 Assert.assertEquals(value, m.invoke(instance), section + ": " + m.getName() + '(' + instance + ") returned invalid value:"); 380 } 381 382 static boolean testHasPublicStaticMethodOpt(String section, @SuppressWarnings("rawtypes") Class type, @SuppressWarnings("rawtypes") Class returnType, String methodName, @SuppressWarnings("rawtypes") Class... paramTypes) { 383 try { 384 testHasPublicStaticMethod(section, type, returnType, methodName, paramTypes); 385 return true; 386 } catch (Exception e) { 387 warnings.append(section).append(": Recommendation failed: Missing method [public static ").append(methodName).append('(') 388 .append(Arrays.toString(paramTypes)).append("):").append(returnType.getName()).append("] on: ").append(type.getName()) 389 .append("\n"); 390 return false; 391 } 392 } 393 394 /** 395 * Test for immutability (optional recommendation), writes a warning if not given. 396 * 397 * @param section 398 * the section of the spec under test 399 * @param type 400 * the type to be checked. 401 * @return true, if the instance is probably immutable. 402 */ 403 public static boolean testImmutableOpt(String section, @SuppressWarnings("rawtypes") Class type) { 404 try { 405 testImmutable(section, type); 406 return true; 407 } catch (Exception e) { 408 warnings.append(section).append(": Recommendation failed: Class should be immutable: ").append(type.getName()).append(", details: ") 409 .append(e.getMessage()).append("\n"); 410 return false; 411 } 412 } 413 414 /** 415 * Test for serializable (optional recommendation), writes a warning if not given. 416 * 417 * @param section 418 * the section of the spec under test 419 * @param type 420 * the type to be checked. 421 * @return true, if the type is probably serializable. 422 */ 423 public static boolean testSerializableOpt(String section, @SuppressWarnings("rawtypes") Class type) { 424 try { 425 testSerializable(section, type); 426 return true; 427 } catch (Exception e) { 428 warnings.append(section).append(": Recommendation failed: Class should be serializable: ").append(type.getName()).append(", details: ") 429 .append(e.getMessage()).append("\n"); 430 return false; 431 } 432 } 433 434 /** 435 * Test for serializable (optional recommendation), writes a warning if not given. 436 * 437 * @param section 438 * the section of the spec under test 439 * @param instance 440 * the object to be checked. 441 * @return true, if the instance is probably serializable. 442 */ 443 public static boolean testSerializableOpt(String section, Object instance) { 444 try { 445 testSerializable(section, instance); 446 return true; 447 } catch (Exception e) { 448 warnings.append(section).append(": Recommendation failed: Class is serializable, but serialization failed: ") 449 .append(instance.getClass().getName()).append("\n"); 450 return false; 451 } 452 } 453 454 static void resetWarnings() { 455 warnings.setLength(0); 456 } 457 458 static String getWarnings() { 459 return warnings.toString(); 460 } 461}