1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.eclipse.org/org/documents/epl-v10.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.ide.common.resources.platform; 17 18 import static com.android.SdkConstants.ANDROID_URI; 19 import static com.android.SdkConstants.DOT_XML; 20 21 import com.android.SdkConstants; 22 import com.android.annotations.NonNull; 23 import com.android.ide.common.api.IAttributeInfo.Format; 24 import com.android.ide.common.resources.ResourceItem; 25 import com.android.ide.common.resources.ResourceRepository; 26 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities; 27 import com.android.ide.eclipse.mock.Mocks; 28 import com.android.io.IAbstractFolder; 29 import com.android.io.IAbstractResource; 30 import com.android.resources.ResourceType; 31 import com.android.utils.StdLogger; 32 import com.google.common.base.Charsets; 33 import com.google.common.collect.ArrayListMultimap; 34 import com.google.common.collect.Multimap; 35 import com.google.common.collect.Sets; 36 import com.google.common.io.Files; 37 38 import junit.framework.TestCase; 39 40 import org.w3c.dom.Attr; 41 import org.w3c.dom.Document; 42 import org.w3c.dom.Element; 43 import org.w3c.dom.NamedNodeMap; 44 45 import java.io.File; 46 import java.io.IOException; 47 import java.util.Collection; 48 import java.util.EnumSet; 49 import java.util.Map; 50 import java.util.Set; 51 52 @SuppressWarnings("javadoc") 53 public class AttributeInfoTest extends TestCase { testSimple()54 public void testSimple() throws Exception { 55 AttributeInfo info = new AttributeInfo("test", EnumSet.noneOf(Format.class)); 56 assertTrue(info.isValid("", null, null)); 57 assertTrue(info.isValid("a b c", null, null)); 58 assertTrue(info.isValid("@android foo bar", null, null)); 59 } 60 testIsValidString()61 public void testIsValidString() throws Exception { 62 AttributeInfo info = new AttributeInfo("test", Format.STRING_SET); 63 assertTrue(info.isValid("", null, null)); 64 assertTrue(info.isValid("a b c", null, null)); 65 assertTrue(info.isValid("@android foo bar", null, null)); 66 } 67 testIsValidBoolean()68 public void testIsValidBoolean() throws Exception { 69 AttributeInfo info = new AttributeInfo("test", Format.BOOLEAN_SET); 70 assertTrue(info.isValid("true", null, null)); 71 assertTrue(info.isValid("false", null, null)); 72 assertFalse(info.isValid("", null, null)); 73 assertTrue(info.isValid("TRUE", null, null)); 74 assertTrue(info.isValid("True", null, null)); 75 assertTrue(info.isValid("FALSE", null, null)); 76 assertTrue(info.isValid("False", null, null)); 77 } 78 testIsValidInteger()79 public void testIsValidInteger() throws Exception { 80 AttributeInfo info = new AttributeInfo("test", Format.INTEGER_SET); 81 assertTrue(info.isValid("0", null, null)); 82 assertTrue(info.isValid("1", null, null)); 83 assertTrue(info.isValid("10", null, null)); 84 assertTrue(info.isValid("-10", null, null)); 85 assertTrue(info.isValid(Integer.toString(Integer.MAX_VALUE), null, null)); 86 87 assertFalse(info.isValid("", null, null)); 88 assertFalse(info.isValid("a", null, null)); 89 assertFalse(info.isValid("a1", null, null)); 90 assertFalse(info.isValid("1a", null, null)); 91 assertFalse(info.isValid("1.0", null, null)); 92 } 93 testIsValidFloat()94 public void testIsValidFloat() throws Exception { 95 AttributeInfo info = new AttributeInfo("test", Format.FLOAT_SET); 96 assertTrue(info.isValid("0", null, null)); 97 assertTrue(info.isValid("1", null, null)); 98 assertTrue(info.isValid("10", null, null)); 99 assertTrue(info.isValid("-10", null, null)); 100 assertTrue(info.isValid("-10.1234", null, null)); 101 assertTrue(info.isValid(".1", null, null)); 102 assertTrue(info.isValid("-.1", null, null)); 103 assertTrue(info.isValid("1.5e22", null, null)); 104 assertTrue(info.isValid(Integer.toString(Integer.MAX_VALUE), null, null)); 105 106 assertFalse(info.isValid("", null, null)); 107 assertFalse(info.isValid(".", null, null)); 108 assertFalse(info.isValid("-.", null, null)); 109 assertFalse(info.isValid("a", null, null)); 110 assertFalse(info.isValid("a1", null, null)); 111 assertFalse(info.isValid("1a", null, null)); 112 } 113 testIsValidDimension()114 public void testIsValidDimension() throws Exception { 115 AttributeInfo info = new AttributeInfo("test", Format.DIMENSION_SET); 116 assertTrue(info.isValid("0dp", null, null)); 117 assertTrue(info.isValid("1dp", null, null)); 118 assertTrue(info.isValid("10dip", null, null)); 119 assertTrue(info.isValid("-10px", null, null)); 120 assertTrue(info.isValid("-10.1234mm", null, null)); 121 assertTrue(info.isValid("14sp", null, null)); 122 assertTrue(info.isValid("72pt", null, null)); 123 124 assertFalse(info.isValid("", null, null)); 125 assertFalse(info.isValid("5", null, null)); 126 assertFalse(info.isValid("50ps", null, null)); 127 // Since we allow resources even when not specified in format, don't assert 128 // this: 129 //assertFalse(info.isValid("@dimen/foo")); 130 } 131 testIsValidColor()132 public void testIsValidColor() throws Exception { 133 AttributeInfo info = new AttributeInfo("test", Format.COLOR_SET); 134 assertTrue(info.isValid("#fff", null, null)); 135 assertTrue(info.isValid("#ffffff", null, null)); 136 assertTrue(info.isValid("#12345678", null, null)); 137 assertTrue(info.isValid("#abcdef00", null, null)); 138 139 assertFalse(info.isValid("", null, null)); 140 assertFalse(info.isValid("#fffffffff", null, null)); 141 assertFalse(info.isValid("red", null, null)); 142 assertFalse(info.isValid("rgb(1,2,3)", null, null)); 143 } 144 testIsValidFraction()145 public void testIsValidFraction() throws Exception { 146 AttributeInfo info = new AttributeInfo("test", EnumSet.<Format>of(Format.FRACTION)); 147 assertTrue(info.isValid("5%", null, null)); 148 assertTrue(info.isValid("25%p", null, null)); 149 150 // We don't validate fractions accurately yet 151 //assertFalse(info.isValid("")); 152 //assertFalse(info.isValid("50%%")); 153 //assertFalse(info.isValid("50")); 154 //assertFalse(info.isValid("-2%")); 155 } 156 testIsValidReference()157 public void testIsValidReference() throws Exception { 158 AttributeInfo info = new AttributeInfo("test", Format.REFERENCE_SET); 159 assertTrue(info.isValid("@android:string/foo", null, null)); 160 assertTrue(info.isValid("@string/foo", null, null)); 161 assertTrue(info.isValid("@dimen/foo", null, null)); 162 assertTrue(info.isValid("@color/foo", null, null)); 163 assertTrue(info.isValid("@animator/foo", null, null)); 164 assertTrue(info.isValid("@anim/foo", null, null)); 165 assertTrue(info.isValid("?android:attr/textAppearanceMedium", null, null)); 166 assertTrue(info.isValid("?textAppearanceMedium", null, null)); 167 168 assertFalse(info.isValid("", null, null)); 169 assertFalse(info.isValid("foo", null, null)); 170 assertFalse(info.isValid("3.4", null, null)); 171 } 172 testIsValidEnum()173 public void testIsValidEnum() throws Exception { 174 AttributeInfo info = new AttributeInfo("test", Format.ENUM_SET); 175 info.setEnumValues(new String[] { "wrap_content", "match_parent" }); 176 assertTrue(info.isValid("wrap_content", null, null)); 177 assertTrue(info.isValid("match_parent", null, null)); 178 assertFalse(info.isValid("", null, null)); 179 assertFalse(info.isValid("other", null, null)); 180 assertFalse(info.isValid("50", null, null)); 181 } 182 testIsValidFlag()183 public void testIsValidFlag() throws Exception { 184 AttributeInfo info = new AttributeInfo("test", Format.FLAG_SET); 185 info.setFlagValues(new String[] { "left", "top", "right", "bottom" }); 186 assertTrue(info.isValid("left", null, null)); 187 assertTrue(info.isValid("top", null, null)); 188 assertTrue(info.isValid("left|top", null, null)); 189 assertTrue(info.isValid("", null, null)); 190 191 assertFalse(info.isValid("other", null, null)); 192 assertFalse(info.isValid("50", null, null)); 193 } 194 testCombined1()195 public void testCombined1() throws Exception { 196 AttributeInfo info = new AttributeInfo("test", EnumSet.<Format>of(Format.INTEGER, 197 Format.REFERENCE)); 198 assertTrue(info.isValid("1", null, null)); 199 assertTrue(info.isValid("@dimen/foo", null, null)); 200 assertFalse(info.isValid("foo", null, null)); 201 } 202 testCombined2()203 public void testCombined2() throws Exception { 204 AttributeInfo info = new AttributeInfo("test", EnumSet.<Format>of(Format.COLOR, 205 Format.REFERENCE)); 206 assertTrue(info.isValid("#ff00ff00", null, null)); 207 assertTrue(info.isValid("@color/foo", null, null)); 208 assertFalse(info.isValid("foo", null, null)); 209 } 210 testCombined3()211 public void testCombined3() throws Exception { 212 AttributeInfo info = new AttributeInfo("test", EnumSet.<Format>of(Format.STRING, 213 Format.REFERENCE)); 214 assertTrue(info.isValid("test", null, null)); 215 assertTrue(info.isValid("@color/foo", null, null)); 216 } 217 testCombined4()218 public void testCombined4() throws Exception { 219 AttributeInfo info = new AttributeInfo("test", EnumSet.<Format>of(Format.ENUM, 220 Format.DIMENSION)); 221 info.setEnumValues(new String[] { "wrap_content", "match_parent" }); 222 assertTrue(info.isValid("wrap_content", null, null)); 223 assertTrue(info.isValid("match_parent", null, null)); 224 assertTrue(info.isValid("50dp", null, null)); 225 assertFalse(info.isValid("50", null, null)); 226 assertFalse(info.isValid("test", null, null)); 227 } 228 testResourcesExist()229 public void testResourcesExist() throws Exception { 230 IAbstractFolder folder = Mocks.createAbstractFolder( 231 SdkConstants.FD_RESOURCES, new IAbstractResource[0]); 232 233 AttributeInfo info = new AttributeInfo("test", Format.REFERENCE_SET); 234 TestResourceRepository projectResources = new TestResourceRepository(folder,false); 235 projectResources.addResource(ResourceType.STRING, "mystring"); 236 projectResources.addResource(ResourceType.DIMEN, "mydimen"); 237 TestResourceRepository frameworkResources = new TestResourceRepository(folder, true); 238 frameworkResources.addResource(ResourceType.LAYOUT, "mylayout"); 239 240 assertTrue(info.isValid("@string/mystring", null, null)); 241 assertTrue(info.isValid("@dimen/mydimen", null, null)); 242 assertTrue(info.isValid("@android:layout/mylayout", null, null)); 243 assertTrue(info.isValid("?android:attr/listPreferredItemHeigh", null, null)); 244 245 assertTrue(info.isValid("@string/mystring", projectResources, frameworkResources)); 246 assertTrue(info.isValid("@dimen/mydimen", projectResources, frameworkResources)); 247 assertTrue(info.isValid("@android:layout/mylayout", projectResources, frameworkResources)); 248 249 assertFalse(info.isValid("@android:string/mystring", projectResources, 250 frameworkResources)); 251 assertFalse(info.isValid("@android:dimen/mydimen", projectResources, frameworkResources)); 252 assertFalse(info.isValid("@layout/mylayout", projectResources, frameworkResources)); 253 assertFalse(info.isValid("@layout/foo", projectResources, frameworkResources)); 254 assertFalse(info.isValid("@anim/foo", projectResources, frameworkResources)); 255 assertFalse(info.isValid("@android:anim/foo", projectResources, frameworkResources)); 256 } 257 258 private class TestResourceRepository extends ResourceRepository { 259 private Multimap<ResourceType, String> mResources = ArrayListMultimap.create(); 260 TestResourceRepository(IAbstractFolder resFolder, boolean isFrameworkRepository)261 protected TestResourceRepository(IAbstractFolder resFolder, boolean isFrameworkRepository) { 262 super(resFolder, isFrameworkRepository); 263 } 264 addResource(ResourceType type, String name)265 void addResource(ResourceType type, String name) { 266 mResources.put(type, name); 267 } 268 269 @Override 270 @NonNull createResourceItem(@onNull String name)271 protected ResourceItem createResourceItem(@NonNull String name) { 272 fail("Not used in test"); 273 return null; 274 } 275 276 @Override hasResourceItem(@onNull ResourceType type, @NonNull String name)277 public boolean hasResourceItem(@NonNull ResourceType type, @NonNull String name) { 278 Collection<String> names = mResources.get(type); 279 if (names != null) { 280 return names.contains(name); 281 } 282 283 return false; 284 } 285 }; 286 287 testIsValid()288 public void testIsValid() throws Exception { 289 // This test loads the full attrs.xml file and then processes a bunch of platform 290 // resource file and makes sure that they're all considered valid. This helps 291 // make sure that isValid() closely matches what aapt accepts. 292 String sdkPath = System.getenv("ADT_SDK_SOURCE_PATH"); 293 assertNotNull("This test requires ADT_SDK_SOURCE_PATH to be set to point to the" + 294 "SDK git repository", sdkPath); 295 File sdk = new File(sdkPath); 296 assertNotNull("$ADT_SDK_SOURCE_PATH (" + sdk.getPath() + ") is not a directory", 297 sdk.isDirectory()); 298 File git = sdk.getParentFile(); 299 File attrsPath = new File(git, "frameworks" + File.separator + "base" 300 + File.separator + "core" + File.separator + "res" + File.separator + "res" 301 + File.separator + "values" + File.separator + "attrs.xml"); 302 assertTrue(attrsPath.getPath(), attrsPath.exists()); 303 AttrsXmlParser parser = new AttrsXmlParser(attrsPath.getPath(), 304 new StdLogger(StdLogger.Level.VERBOSE), 1100); 305 parser.preload(); 306 Map<String, AttributeInfo> attributeMap = parser.getAttributeMap(); 307 assertNotNull(attributeMap); 308 assertNotNull(attributeMap.get("layout_width")); 309 Set<String> seen = Sets.newHashSet(); 310 311 checkDir(new File(git, "packages" + File.separator + "apps"), false, attributeMap, seen); 312 } 313 checkDir(File dir, boolean isResourceDir, Map<String, AttributeInfo> map, Set<String> seen)314 private void checkDir(File dir, boolean isResourceDir, 315 Map<String, AttributeInfo> map, Set<String> seen) throws IOException { 316 assertTrue(dir.isDirectory()); 317 File[] list = dir.listFiles(); 318 if (list != null) { 319 for (File file : list) { 320 if (isResourceDir && file.isFile() && file.getPath().endsWith(DOT_XML)) { 321 checkXmlFile(file, map, seen); 322 } else if (file.isDirectory()) { 323 checkDir(file, isResourceDir || file.getName().equals("res"), map, seen); 324 } 325 } 326 } 327 } 328 checkXmlFile(File file, Map<String, AttributeInfo> map, Set<String> seen)329 private void checkXmlFile(File file, Map<String, AttributeInfo> map, 330 Set<String> seen) throws IOException { 331 String xml = Files.toString(file, Charsets.UTF_8); 332 if (xml != null) { 333 //Document doc = DomUtilities.parseStructuredDocument(xml); 334 Document doc = DomUtilities.parseDocument(xml, false); 335 if (doc != null && doc.getDocumentElement() != null) { 336 checkElement(file, doc.getDocumentElement(), map, seen); 337 } 338 } 339 } 340 checkElement(File file, Element element, Map<String, AttributeInfo> map, Set<String> seen)341 private void checkElement(File file, Element element, Map<String, AttributeInfo> map, 342 Set<String> seen) { 343 NamedNodeMap attributes = element.getAttributes(); 344 for (int i = 0, n = attributes.getLength(); i < n; i++) { 345 Attr attribute = (Attr) attributes.item(i); 346 347 String uri = attribute.getNamespaceURI(); 348 String name = attribute.getLocalName(); 349 String value = attribute.getValue(); 350 if (ANDROID_URI.equals(uri)) { 351 AttributeInfo info = map.get(name); 352 if (info == null) { 353 System.out.println("Warning: Unknown attribute '" + name + "' in " + file); 354 return; 355 } 356 if (!info.isValid(value, null, null)) { 357 if (name.equals("duration") || name.equals("exitFadeDuration")) { 358 // Already known 359 return; 360 } 361 String message = "In file " + 362 file.getPath() + ":\nCould not validate value \"" + value 363 + "\" for attribute '" 364 + name + "' where the attribute info has formats " + info.getFormats() 365 + "\n"; 366 System.out.println("\n" + message); 367 fail(message); 368 } 369 if ((value.startsWith("@") || value.startsWith("?")) && 370 !info.getFormats().contains(Format.REFERENCE)) { 371 // Print out errors in attrs.xml 372 373 if (!seen.contains(name)) { 374 seen.add(name); 375 System.out.println("\"" + name + "\" with formats " + info.getFormats() 376 + " was passed a reference (" + value + ") in file " + file); 377 } 378 } 379 } 380 } 381 } 382 } 383