001/* 002 * Units of Measurement TCK 003 * Copyright © 2005-2017, Jean-Marie Dautelle, Werner Keil, V2COM. 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:units@catmedia.us">Werner Keil</a> 067 * @version 1.0.2, September 10, 2017 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 * @throws 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 214 * @param type 215 * @param returnType 216 * @param name 217 * @param paramTypes 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 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 241 * @param type 242 * @param trySuperclassFirst 243 * @param returnType 244 * @param name 245 * @param paramTypes 246 * @deprecated use the simplified version on top of Reflections.org where possible 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 * 289 * @param section 290 * @param type 291 * @param name 292 * @deprecated use the simplified version on top of Reflections.org where possible 293 */ 294 public static void testHasPublicMethod(String section, Class<?> type, String name) { 295 testHasPublicMethod(section, type, name, false); 296 } 297 298 /** 299 * Tests if the given type has a public static method with the given signature. 300 * 301 * @param section 302 * the section of the spec under test 303 * @param type 304 * the type to be checked. 305 * @param returnType 306 * the method return type. 307 * @param name 308 * the method name 309 * @param paramTypes 310 * the parameter types. 311 * @throws TCKValidationException 312 * if test fails. 313 */ 314 @SuppressWarnings("rawtypes") 315 static void testHasPublicStaticMethod(String section, Class type, Class returnType, String name, Class... paramTypes) { 316 Class current = type; 317 while (current != null) { 318 for (Method m : current.getDeclaredMethods()) { 319 if (returnType.equals(returnType) && m.getName().equals(name) && ((m.getModifiers() & PUBLIC) != 0) 320 && ((m.getModifiers() & Modifier.STATIC) != 0) && Arrays.equals(m.getParameterTypes(), paramTypes)) { 321 return; 322 } 323 } 324 current = current.getSuperclass(); 325 } 326 throw new TCKValidationException(section + ": Class must implement method " + name + '(' + Arrays.toString(paramTypes) + "): " 327 + returnType.getName() + ", but does not: " + type.getName()); 328 } 329 330 /** 331 * Tests if the given type has not a public method with the given signature. 332 * 333 * @param section 334 * the section of the spec under test 335 * @param type 336 * the type to be checked. 337 * @param returnType 338 * the method return type. 339 * @param name 340 * the method name 341 * @param paramTypes 342 * the parameter types. 343 * @throws TCKValidationException 344 * if test fails. 345 */ 346 public static void testHasNotPublicMethod(String section, Class type, Class returnType, String name, Class... paramTypes) { 347 Class current = type; 348 while (current != null) { 349 for (Method m : current.getDeclaredMethods()) { 350 if (returnType.equals(returnType) && m.getName().equals(name) && Arrays.equals(m.getParameterTypes(), paramTypes)) { 351 throw new TCKValidationException(section + ": Class must NOT implement method " + name + '(' + Arrays.toString(paramTypes) + "): " 352 + returnType.getName() + ", but does: " + type.getName()); 353 } 354 } 355 current = current.getSuperclass(); 356 } 357 } 358 359 /** 360 * Checks the returned value, when calling a given method. 361 * 362 * @param section 363 * the section of the spec under test 364 * @param value 365 * the expected value 366 * @param methodName 367 * the target method name 368 * @param instance 369 * the instance to call 370 * @throws NoSuchMethodException 371 * @throws SecurityException 372 * @throws IllegalAccessException 373 * @throws IllegalArgumentException 374 * @throws InvocationTargetException 375 * @throws TCKValidationException 376 * if test fails. 377 */ 378 public static void assertValue(String section, Object value, String methodName, Object instance) 379 throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { 380 Method m = instance.getClass().getDeclaredMethod(methodName); 381 Assert.assertEquals(value, m.invoke(instance), section + ": " + m.getName() + '(' + instance + ") returned invalid value:"); 382 } 383 384 static boolean testHasPublicStaticMethodOpt(String section, Class type, Class returnType, String methodName, Class... paramTypes) { 385 try { 386 testHasPublicStaticMethod(section, type, returnType, methodName, paramTypes); 387 return true; 388 } catch (Exception e) { 389 warnings.append(section).append(": Recommendation failed: Missing method [public static ").append(methodName).append('(') 390 .append(Arrays.toString(paramTypes)).append("):").append(returnType.getName()).append("] on: ").append(type.getName()) 391 .append("\n"); 392 return false; 393 } 394 } 395 396 /** 397 * Test for immutability (optional recommendation), writes a warning if not given. 398 * 399 * @param section 400 * the section of the spec under test 401 * @param type 402 * the type to be checked. 403 * @return true, if the instance is probably immutable. 404 */ 405 public static boolean testImmutableOpt(String section, Class type) { 406 try { 407 testImmutable(section, type); 408 return true; 409 } catch (Exception e) { 410 warnings.append(section).append(": Recommendation failed: Class should be immutable: ").append(type.getName()).append(", details: ") 411 .append(e.getMessage()).append("\n"); 412 return false; 413 } 414 } 415 416 /** 417 * Test for serializable (optional recommendation), writes a warning if not given. 418 * 419 * @param section 420 * the section of the spec under test 421 * @param type 422 * the type to be checked. 423 * @return true, if the type is probably serializable. 424 */ 425 public static boolean testSerializableOpt(String section, Class type) { 426 try { 427 testSerializable(section, type); 428 return true; 429 } catch (Exception e) { 430 warnings.append(section).append(": Recommendation failed: Class should be serializable: ").append(type.getName()).append(", details: ") 431 .append(e.getMessage()).append("\n"); 432 return false; 433 } 434 } 435 436 /** 437 * Test for serializable (optional recommendation), writes a warning if not given. 438 * 439 * @param section 440 * the section of the spec under test 441 * @param instance 442 * the object to be checked. 443 * @return true, if the instance is probably serializable. 444 */ 445 public static boolean testSerializableOpt(String section, Object instance) { 446 try { 447 testSerializable(section, instance); 448 return true; 449 } catch (Exception e) { 450 warnings.append(section).append(": Recommendation failed: Class is serializable, but serialization failed: ") 451 .append(instance.getClass().getName()).append("\n"); 452 return false; 453 } 454 } 455 456 static void resetWarnings() { 457 warnings.setLength(0); 458 } 459 460 static String getWarnings() { 461 return warnings.toString(); 462 } 463}