001/* 002 * Units of Measurement TCK 003 * Copyright © 2005-2019, 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 tech.units.tck.TCKValidationException; 058 059import javax.inject.Singleton; 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 1.4, July 7, 2019 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 * @deprecated use the simplified version on top of Reflections.org where possible 219 */ 220 public static void testHasPublicMethod(String section, Class<?> type, Class<?> returnType, String name, Class<?>... paramTypes) { 221 Class<?> current = type; 222 while (current != null) { 223 for (Method m : current.getDeclaredMethods()) { 224 if (returnType.equals(returnType) && m.getName().equals(name) && ((m.getModifiers() & PUBLIC) != 0) 225 && Arrays.equals(m.getParameterTypes(), paramTypes)) { 226 return; 227 } 228 } 229 current = current.getSuperclass(); 230 } 231 throw new TCKValidationException(section + ": Class must implement method " + name + '(' + Arrays.toString(paramTypes) + "): " 232 + returnType.getName() + ", but does not: " + type.getName()); 233 } 234 235 @SuppressWarnings("rawtypes") 236 private static final List<Class> PRIMITIVE_CLASSES = Collections 237 .unmodifiableList(Arrays.asList(new Class[] { Object.class, Number.class, Enum.class })); 238 239 /** 240 * 241 * @param section the section of the specification 242 * @param type the data type 243 * @param trySuperclassFirst if tht super class if available should be tested first 244 * @param returnType the expected return type 245 * @param name the name of the method 246 * @param paramTypes types of parameters 247 */ 248 public static void testHasPublicMethod(String section, Class<?> type, boolean trySuperclassFirst, Class<?> returnType, String name, 249 Class<?>... paramTypes) { 250 if (trySuperclassFirst && type.getSuperclass() != null) { 251 if (PRIMITIVE_CLASSES.contains(type.getSuperclass())) { 252 testHasPublicMethod(section, type, returnType, name, paramTypes); 253 } else { 254 testHasPublicMethod(section, type.getSuperclass(), returnType, name, paramTypes); 255 } 256 } else { 257 testHasPublicMethod(section, type, returnType, name, paramTypes); 258 } 259 } 260 261 /** 262 * Tests if the given type has a public method with the given signature. 263 * 264 * @param section 265 * the section of the spec under test 266 * @param type 267 * the type to be checked. 268 * @param name 269 * the method name 270 * @param hasParameters 271 * the method has parameters. 272 * @throws TCKValidationException 273 * if test fails. 274 */ 275 @SuppressWarnings({ "unchecked" }) 276 public static void testHasPublicMethod(String section, Class<?> type, String name, boolean hasParameters) { 277 Set<Method> getters; 278 if (hasParameters) { 279 getters = getAllMethods(type, withModifier(PUBLIC), withName(name)); 280 } else { 281 getters = getAllMethods(type, withModifier(PUBLIC), withName(name), withParametersCount(0)); 282 } 283 assertThat(getters.size(), greaterThanOrEqualTo(1)); // interface plus 284 // at least one implementation 285 } 286 287 /** 288 * @param section the section of the specification 289 * @param type the data type 290 * @param name the name of the method 291 */ 292 public static void testHasPublicMethod(String section, Class<?> type, String name) { 293 testHasPublicMethod(section, type, name, false); 294 } 295 296 /** 297 * Tests if the given type has a public static method with the given signature. 298 * 299 * @param section 300 * the section of the spec under test 301 * @param type 302 * the type to be checked. 303 * @param returnType 304 * the method return type. 305 * @param name 306 * the method name 307 * @param paramTypes 308 * the parameter types. 309 * @throws TCKValidationException 310 * if test fails. 311 */ 312 @SuppressWarnings("rawtypes") 313 static void testHasPublicStaticMethod(String section, Class type, Class returnType, String name, Class... paramTypes) { 314 Class current = type; 315 while (current != null) { 316 for (Method m : current.getDeclaredMethods()) { 317 if (returnType.equals(returnType) && m.getName().equals(name) && ((m.getModifiers() & PUBLIC) != 0) 318 && ((m.getModifiers() & Modifier.STATIC) != 0) && Arrays.equals(m.getParameterTypes(), paramTypes)) { 319 return; 320 } 321 } 322 current = current.getSuperclass(); 323 } 324 throw new TCKValidationException(section + ": Class must implement method " + name + '(' + Arrays.toString(paramTypes) + "): " 325 + returnType.getName() + ", but does not: " + type.getName()); 326 } 327 328 /** 329 * Tests if the given type has not a public method with the given signature. 330 * 331 * @param section 332 * the section of the spec under test 333 * @param type 334 * the type to be checked. 335 * @param returnType 336 * the method return type. 337 * @param name 338 * the method name 339 * @param paramTypes 340 * the parameter types. 341 * @throws TCKValidationException 342 * if test fails. 343 */ 344 @SuppressWarnings("rawtypes") 345 public static void testHasNotPublicMethod(String section, Class type, Class returnType, String name, Class... paramTypes) { 346 Class current = type; 347 while (current != null) { 348 for (Method m : current.getDeclaredMethods()) { 349 if (returnType.equals(returnType) && m.getName().equals(name) && Arrays.equals(m.getParameterTypes(), paramTypes)) { 350 throw new TCKValidationException(section + ": Class must NOT implement method " + name + '(' + Arrays.toString(paramTypes) + "): " 351 + returnType.getName() + ", but does: " + type.getName()); 352 } 353 } 354 current = current.getSuperclass(); 355 } 356 } 357 358 /** 359 * Checks the returned value, when calling a given method. 360 * 361 * @param section 362 * the section of the spec under test 363 * @param value 364 * the expected value 365 * @param methodName 366 * the target method name 367 * @param instance 368 * the instance to call 369 * @throws NoSuchMethodException if no method with the given name exists. 370 * @throws SecurityException if a security problem occurs. 371 * @throws IllegalAccessException if the method may not be called, e.g. due to security constraints. 372 * @throws IllegalArgumentException if a wrong or inappropriate argument was provided. 373 * @throws InvocationTargetException if an exception thrown by an invoked method or constructor. 374 * @throws TCKValidationException 375 * if test fails. 376 */ 377 public static void assertValue(String section, Object value, String methodName, Object instance) 378 throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { 379 Method m = instance.getClass().getDeclaredMethod(methodName); 380 Assert.assertEquals(value, m.invoke(instance), section + ": " + m.getName() + '(' + instance + ") returned invalid value:"); 381 } 382 383 static boolean testHasPublicStaticMethodOpt(String section, @SuppressWarnings("rawtypes") Class type, @SuppressWarnings("rawtypes") Class returnType, String methodName, @SuppressWarnings("rawtypes") Class... paramTypes) { 384 try { 385 testHasPublicStaticMethod(section, type, returnType, methodName, paramTypes); 386 return true; 387 } catch (Exception e) { 388 warnings.append(section).append(": Recommendation failed: Missing method [public static ").append(methodName).append('(') 389 .append(Arrays.toString(paramTypes)).append("):").append(returnType.getName()).append("] on: ").append(type.getName()) 390 .append("\n"); 391 return false; 392 } 393 } 394 395 /** 396 * Test for immutability (optional recommendation), writes a warning if not given. 397 * 398 * @param section 399 * the section of the spec under test 400 * @param type 401 * the type to be checked. 402 * @return true, if the instance is probably immutable. 403 */ 404 public static boolean testImmutableOpt(String section, @SuppressWarnings("rawtypes") Class type) { 405 try { 406 testImmutable(section, type); 407 return true; 408 } catch (Exception e) { 409 warnings.append(section).append(": Recommendation failed: Class should be immutable: ").append(type.getName()).append(", details: ") 410 .append(e.getMessage()).append("\n"); 411 return false; 412 } 413 } 414 415 /** 416 * Test for serializable (optional recommendation), writes a warning if not given. 417 * 418 * @param section 419 * the section of the spec under test 420 * @param type 421 * the type to be checked. 422 * @return true, if the type is probably serializable. 423 */ 424 public static boolean testSerializableOpt(String section, @SuppressWarnings("rawtypes") Class type) { 425 try { 426 testSerializable(section, type); 427 return true; 428 } catch (Exception e) { 429 warnings.append(section).append(": Recommendation failed: Class should be serializable: ").append(type.getName()).append(", details: ") 430 .append(e.getMessage()).append("\n"); 431 return false; 432 } 433 } 434 435 /** 436 * Test for serializable (optional recommendation), writes a warning if not given. 437 * 438 * @param section 439 * the section of the spec under test 440 * @param instance 441 * the object to be checked. 442 * @return true, if the instance is probably serializable. 443 */ 444 public static boolean testSerializableOpt(String section, Object instance) { 445 try { 446 testSerializable(section, instance); 447 return true; 448 } catch (Exception e) { 449 warnings.append(section).append(": Recommendation failed: Class is serializable, but serialization failed: ") 450 .append(instance.getClass().getName()).append("\n"); 451 return false; 452 } 453 } 454 455 static void resetWarnings() { 456 warnings.setLength(0); 457 } 458 459 static String getWarnings() { 460 return warnings.toString(); 461 } 462}