/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.tools.lint.checks; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.tools.lint.detector.api.Detector; import com.android.tools.lint.detector.api.Severity; import java.io.File; import java.io.PrintWriter; import java.io.RandomAccessFile; import java.io.StringWriter; @SuppressWarnings("javadoc") public class ApiLookupTest extends AbstractCheckTest { private final ApiLookup mDb = ApiLookup.get(new TestLintClient()); public void test1() { assertEquals(5, mDb.getFieldVersion("android/Manifest$permission", "AUTHENTICATE_ACCOUNTS")); assertTrue(mDb.getFieldVersion("android/R$attr", "absListViewStyle") <= 1); assertEquals(11, mDb.getFieldVersion("android/R$attr", "actionMenuTextAppearance")); assertEquals(5, mDb.getCallVersion("android/graphics/drawable/BitmapDrawable", "<init>", "(Landroid/content/res/Resources;Ljava/lang/String;)V")); assertEquals(4, mDb.getCallVersion("android/graphics/drawable/BitmapDrawable", "setTargetDensity", "(Landroid/util/DisplayMetrics;)V")); assertEquals(7, mDb.getClassVersion("android/app/WallpaperInfo")); assertEquals(11, mDb.getClassVersion("android/widget/StackView")); assertTrue(mDb.getClassVersion("ava/text/ChoiceFormat") <= 1); // Class lookup: Unknown class assertEquals(-1, mDb.getClassVersion("foo/Bar")); // Field lookup: Unknown class assertEquals(-1, mDb.getFieldVersion("foo/Bar", "FOOBAR")); // Field lookup: Unknown field assertEquals(-1, mDb.getFieldVersion("android/Manifest$permission", "FOOBAR")); // Method lookup: Unknown class assertEquals(-1, mDb.getCallVersion("foo/Bar", "<init>", "(Landroid/content/res/Resources;Ljava/lang/String;)V")); // Method lookup: Unknown name assertEquals(-1, mDb.getCallVersion("android/graphics/drawable/BitmapDrawable", "foo", "(Landroid/content/res/Resources;Ljava/lang/String;)V")); // Method lookup: Unknown argument list assertEquals(-1, mDb.getCallVersion("android/graphics/drawable/BitmapDrawable", "<init>", "(I)V")); } public void test2() { // Regression test: // This used to return 11 because of some wildcard syntax in the signature assertTrue(mDb.getCallVersion("java/lang/Object", "getClass", "()") <= 1); } public void testIssue26467() { assertTrue(mDb.getCallVersion("java/nio/ByteBuffer", "array", "()") <= 1); assertEquals(9, mDb.getCallVersion("java/nio/Buffer", "array", "()")); } public void testNoInheritedConstructors() { assertTrue(mDb.getCallVersion("java/util/zip/ZipOutputStream", "<init>", "()") <= 1); assertTrue(mDb.getCallVersion("android/app/AliasActivity", "<init>", "(Landroid/content/Context;I)") <= 1); } public void testIssue35190() { assertEquals(9, mDb.getCallVersion("java/io/IOException", "<init>", "(Ljava/lang/Throwable;)V")); } public void testInheritInterfaces() { // The onPreferenceStartFragment is inherited via the // android/preference/PreferenceFragment$OnPreferenceStartFragmentCallback // interface assertEquals(11, mDb.getCallVersion("android/preference/PreferenceActivity", "onPreferenceStartFragment", "(Landroid/preference/PreferenceFragment;Landroid/preference/Preference;)")); } public void testIsValidPackage() { assertTrue(mDb.isValidJavaPackage("java/lang/Integer")); assertTrue(mDb.isValidJavaPackage("javax/crypto/Cipher")); assertTrue(mDb.isValidJavaPackage("java/awt/font/NumericShaper")); assertFalse(mDb.isValidJavaPackage("javax/swing/JButton")); assertFalse(mDb.isValidJavaPackage("java/rmi/Naming")); assertFalse(mDb.isValidJavaPackage("java/lang/instrument/Instrumentation")); } @Override protected Detector getDetector() { fail("This is not used in the ApiDatabase test"); return null; } private File mCacheDir; @SuppressWarnings("StringBufferField") private StringBuilder mLogBuffer = new StringBuilder(); @SuppressWarnings({"ConstantConditions", "IOResourceOpenedButNotSafelyClosed", "ResultOfMethodCallIgnored"}) public void testCorruptedCacheHandling() throws Exception { ApiLookup lookup; // Real cache: mCacheDir = new TestLintClient().getCacheDir(true); mLogBuffer.setLength(0); lookup = ApiLookup.get(new LookupTestClient()); assertEquals(11, lookup.getFieldVersion("android/R$attr", "actionMenuTextAppearance")); assertNotNull(lookup); assertEquals("", mLogBuffer.toString()); // No warnings ApiLookup.dispose(); // Custom cache dir: should also work mCacheDir = new File(getTempDir(), "test-cache"); mCacheDir.mkdirs(); mLogBuffer.setLength(0); lookup = ApiLookup.get(new LookupTestClient()); assertEquals(11, lookup.getFieldVersion("android/R$attr", "actionMenuTextAppearance")); assertNotNull(lookup); assertEquals("", mLogBuffer.toString()); // No warnings ApiLookup.dispose(); // Now truncate cache file File cacheFile = new File(mCacheDir, ApiLookup.getCacheFileName("api-versions.xml", ApiLookup.getPlatformVersion(new LookupTestClient()))); //$NON-NLS-1$ mLogBuffer.setLength(0); assertTrue(cacheFile.exists()); RandomAccessFile raf = new RandomAccessFile(cacheFile, "rw"); // Truncate file in half raf.setLength(100); // Broken header raf.close(); lookup = ApiLookup.get(new LookupTestClient()); String message = mLogBuffer.toString(); // NOTE: This test is incompatible with the DEBUG_FORCE_REGENERATE_BINARY and WRITE_STATS // flags in the ApiLookup class, so if the test fails during development and those are // set, clear them. assertTrue(message.contains("Please delete the file and restart the IDE/lint:")); assertTrue(message.contains(mCacheDir.getPath())); ApiLookup.dispose(); mLogBuffer.setLength(0); assertTrue(cacheFile.exists()); raf = new RandomAccessFile(cacheFile, "rw"); // Truncate file in half in the data portion raf.setLength(raf.length() / 2); raf.close(); lookup = ApiLookup.get(new LookupTestClient()); // This data is now truncated: lookup returns the wrong size. try { assertNotNull(lookup); lookup.getFieldVersion("android/R$attr", "actionMenuTextAppearance"); fail("Can't look up bogus data"); } catch (Throwable t) { // Expected this: the database is corrupted. } assertTrue(message.contains("Please delete the file and restart the IDE/lint:")); assertTrue(message.contains(mCacheDir.getPath())); ApiLookup.dispose(); mLogBuffer.setLength(0); assertTrue(cacheFile.exists()); raf = new RandomAccessFile(cacheFile, "rw"); // Truncate file to 0 bytes raf.setLength(0); raf.close(); lookup = ApiLookup.get(new LookupTestClient()); assertEquals(11, lookup.getFieldVersion("android/R$attr", "actionMenuTextAppearance")); assertNotNull(lookup); assertEquals("", mLogBuffer.toString()); // No warnings ApiLookup.dispose(); } private final class LookupTestClient extends TestLintClient { @SuppressWarnings("ResultOfMethodCallIgnored") @Override public File getCacheDir(boolean create) { assertNotNull(mCacheDir); if (create && !mCacheDir.exists()) { mCacheDir.mkdirs(); } return mCacheDir; } @Override public void log( @NonNull Severity severity, @Nullable Throwable exception, @Nullable String format, @Nullable Object... args) { if (format != null) { mLogBuffer.append(String.format(format, args)); mLogBuffer.append('\n'); } if (exception != null) { StringWriter writer = new StringWriter(); exception.printStackTrace(new PrintWriter(writer)); mLogBuffer.append(writer.toString()); mLogBuffer.append('\n'); } } @Override public void log(Throwable exception, String format, Object... args) { log(Severity.WARNING, exception, format, args); } } }