1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.content.browser; 6 7 import android.test.InstrumentationTestCase; 8 import android.test.suitebuilder.annotation.SmallTest; 9 10 import org.chromium.base.test.util.Feature; 11 import org.chromium.content.browser.DownloadInfo.Builder; 12 13 import java.lang.reflect.InvocationTargetException; 14 import java.lang.reflect.Method; 15 import java.util.HashMap; 16 import java.util.Random; 17 18 /** 19 * Tests for (@link DownloadInfo}. 20 */ 21 public class DownloadInfoTest extends InstrumentationTestCase { 22 /** 23 * Class to store getter/setter information. Stores the method name of a getter and setter 24 * without prefix, so "int getFoo()" and "void setFoo(int)" both have same AccessorSignature 25 * {"Foo", int}. 26 */ 27 static class AccessorSignature { 28 final String mMethodNameWithoutPrefix; 29 final Class<?> mReturnTypeOrParam; 30 31 AccessorSignature(String methodNameWithoutPrefix, Class<?> returnTypeOrParam) { 32 mMethodNameWithoutPrefix = methodNameWithoutPrefix; 33 mReturnTypeOrParam = returnTypeOrParam; 34 } 35 36 @Override 37 public int hashCode() { 38 return mMethodNameWithoutPrefix.hashCode() * 31 39 + mReturnTypeOrParam.hashCode(); 40 } 41 42 @Override 43 public boolean equals(Object obj) { 44 if (obj == this) { 45 return true; 46 } 47 if (obj instanceof AccessorSignature) { 48 AccessorSignature other = (AccessorSignature) obj; 49 return other.mReturnTypeOrParam == mReturnTypeOrParam 50 && other.mMethodNameWithoutPrefix.equals(mMethodNameWithoutPrefix); 51 } 52 return false; 53 } 54 55 @Override 56 public String toString() { 57 return "{ " + mMethodNameWithoutPrefix + ", " + mReturnTypeOrParam.getName() + "}"; 58 } 59 } 60 61 /** 62 * Returns an AccessorInfo object for the method if it is a getter. A getter for the class has 63 * signature: Type getFoo(), where Type is a String or a primitive type other than boolean. 64 * Boolean getters are an exception and have special prefixes. In case of a boolean getter, the 65 * signature is 'boolean isFoo()' or 'boolean hasFoo', i.e. boolean getters start with "is" or 66 * "has". 67 * @param method the method that is a getter 68 * @return AccessorInfo for the method if it is a getter, null otherwise. 69 */ 70 AccessorSignature getGetterInfo(Method method) { 71 // A getter is of format Type getFoo(): i.e. 0 params and one 72 // return type. 73 if (method.getParameterTypes().length == 0) { 74 // Based on return type extract the name of the getter. 75 Class<?> returnType = method.getReturnType(); 76 if (returnType.isPrimitive() || returnType == String.class) { 77 String methodName = method.getName(); 78 if (returnType.equals(Boolean.TYPE)) { 79 if (methodName.matches("(is|has).*")) { 80 return new AccessorSignature(methodName.replaceFirst("is|has", ""), 81 returnType); 82 } 83 } else { 84 if (methodName.startsWith("get")) { 85 return new AccessorSignature(methodName.substring(3), returnType); 86 } 87 } 88 } 89 } 90 return null; 91 } 92 93 /** 94 * Returns an AccessorInfo object for the method if it is a setter. A setter for the class has 95 * signature: Type setFoo(), where Type is a String or a primitive type. 96 * @param method the method that is a getter 97 * @return AccessorInfo for the method if it is a getter, null otherwise. 98 */ 99 AccessorSignature getSetterInfo(Method method) { 100 if (method.getParameterTypes().length == 1) { 101 Class<?> parameter = method.getParameterTypes()[0]; 102 String methodName = method.getName(); 103 if (methodName.startsWith("set")) { 104 if (parameter.equals(Boolean.TYPE)) { 105 // Boolean setters are of form setIsFoo or setHasFoo. 106 return new AccessorSignature( 107 methodName.replaceFirst("set(Is|Has)", ""), 108 parameter); 109 } else { 110 return new AccessorSignature(methodName.substring(3), parameter); 111 } 112 } 113 } 114 return null; 115 } 116 117 /** 118 * Invoke a method via reflection and rethrow the exception. Makes findbugs happy. 119 * @param method Method to invoke. 120 * @param instance class instance on which method should be invoked. 121 * @return return value of invocation. 122 */ 123 Object invokeMethod(Method method, Object instance, Object... args) throws Exception { 124 try { 125 return method.invoke(instance, args); 126 } catch (IllegalArgumentException e) { 127 throw e; 128 } catch (IllegalAccessException e) { 129 throw e; 130 } catch (InvocationTargetException e) { 131 throw e; 132 } 133 } 134 135 @SmallTest 136 @Feature({"Downloads"}) 137 public void testBuilderHasCorrectSetters() { 138 HashMap<AccessorSignature, Method> downloadInfoGetters = 139 new HashMap<AccessorSignature, Method>(); 140 HashMap<AccessorSignature, Method> builderSetters = 141 new HashMap<AccessorSignature, Method>(); 142 for (Method m : DownloadInfo.class.getMethods()) { 143 AccessorSignature info = getGetterInfo(m); 144 if (info != null) { 145 downloadInfoGetters.put(info, m); 146 } 147 } 148 assertTrue("There should be at least one getter.", 149 downloadInfoGetters.size() > 0); 150 for (Method m : Builder.class.getMethods()) { 151 AccessorSignature info = getSetterInfo(m); 152 if (info != null) { 153 builderSetters.put(info, m); 154 } 155 } 156 157 // Make sure we have a setter for each getter and vice versa. 158 assertEquals("Mismatch between getters and setters.", 159 downloadInfoGetters.keySet(), builderSetters.keySet()); 160 161 // Generate specific values for fields and verify that they are correctly set. For boolean 162 // fields set them all to true. For integers generate random numbers. 163 Random random = new Random(); 164 HashMap<AccessorSignature, Object> valuesForBuilder = 165 new HashMap<DownloadInfoTest.AccessorSignature, Object>(); 166 for (AccessorSignature signature : builderSetters.keySet()) { 167 if (signature.mReturnTypeOrParam.equals(String.class)) { 168 String value = signature.mMethodNameWithoutPrefix 169 + Integer.toString(random.nextInt()); 170 valuesForBuilder.put(signature, value); 171 } else if (signature.mReturnTypeOrParam.equals(Boolean.TYPE)) { 172 valuesForBuilder.put(signature, Boolean.TRUE); 173 } else { 174 // This is a primitive type that is not boolean, probably an integer. 175 valuesForBuilder.put(signature, Integer.valueOf(random.nextInt(100))); 176 } 177 } 178 179 Builder builder = new Builder(); 180 // Create a DownloadInfo object with these values. 181 for (AccessorSignature signature : builderSetters.keySet()) { 182 Method setter = builderSetters.get(signature); 183 try { 184 invokeMethod(setter, builder, valuesForBuilder.get(signature)); 185 } catch (Exception e) { 186 fail("Exception while setting value in the setter. Signature: " + signature 187 + " value:" + valuesForBuilder.get(signature) + ":" + e); 188 } 189 } 190 DownloadInfo downloadInfo = builder.build(); 191 for (AccessorSignature signature : downloadInfoGetters.keySet()) { 192 Method getter = downloadInfoGetters.get(signature); 193 try { 194 Object returnValue = invokeMethod(getter, downloadInfo); 195 assertEquals(signature.toString(), 196 valuesForBuilder.get(signature).toString(), returnValue.toString()); 197 } catch (Exception e) { 198 fail("Exception while getting value from getter. Signature: " + signature 199 + " value:" + valuesForBuilder.get(signature)); 200 } 201 } 202 203 // Test DownloadInfo.fromDownloadInfo copies all fields. 204 DownloadInfo newDownloadInfo = Builder.fromDownloadInfo(downloadInfo).build(); 205 for (AccessorSignature signature : downloadInfoGetters.keySet()) { 206 Method getter = downloadInfoGetters.get(signature); 207 try { 208 Object returnValue1 = invokeMethod(getter, downloadInfo); 209 Object returnValue2 = invokeMethod(getter, newDownloadInfo); 210 assertEquals(signature.toString(), returnValue1, returnValue2); 211 } catch (Exception e) { 212 fail("Exception while getting value from getter. Signature: " + signature 213 + " value:" + valuesForBuilder.get(signature) + ":" + e); 214 } 215 } 216 } 217 } 218