diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests')
9 files changed, 1244 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/AdtTestData.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/AdtTestData.java new file mode 100644 index 000000000..c86f4cf5f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/AdtTestData.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.ide.eclipse.tests; + +import com.android.SdkConstants; +import com.android.ide.eclipse.adt.AdtConstants; +import com.android.ide.eclipse.adt.AdtPlugin; + +import org.eclipse.core.runtime.FileLocator; +import org.eclipse.core.runtime.Platform; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.logging.Logger; + +/** + * Helper class for retrieving test data + * <p/> + * All tests which need to retrieve paths to test data files should go through this class. + */ +public class AdtTestData { + + /** singleton instance */ + private static AdtTestData sInstance = null; + private static final Logger sLogger = Logger.getLogger(AdtTestData.class.getName()); + + /** The prefered directory separator to use. */ + private static final String DIR_SEP_STR = "/"; + private static final char DIR_SEP_CHAR = '/'; + + /** The absolute file path to the plugin's contents. */ + private String mOsRootDataPath; + + private AdtTestData() { + // can set test_data env variable to override default behavior of + // finding data using class loader + // useful when running in plugin environment, where test data is inside + // bundled jar, and must be extracted to temp filesystem location to be + // accessed normally + mOsRootDataPath = System.getProperty("test_data"); + if (mOsRootDataPath == null) { + sLogger.info("Cannot find test_data environment variable, init to class loader"); + URL url = this.getClass().getClassLoader().getResource("."); //$NON-NLS-1$ + + if (Platform.isRunning()) { + sLogger.info("Running as an Eclipse Plug-in JUnit test, using FileLocator"); + try { + mOsRootDataPath = FileLocator.resolve(url).getFile(); + if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) { + // Fix the path returned by the URL resolver + // so that it actually works on Windows. + + // First, Windows paths don't start with a / especially + // if they contain a drive spec such as C:/... + int pos = mOsRootDataPath.indexOf(':'); + if (pos > 0 && mOsRootDataPath.charAt(0) == '/') { + mOsRootDataPath = mOsRootDataPath.substring(1); + } + + // Looking for "." probably inserted a /./, so clean it up + mOsRootDataPath = mOsRootDataPath.replace("/./", "/"); + } + } catch (IOException e) { + sLogger.warning("IOException while using FileLocator, reverting to url"); + mOsRootDataPath = url.getFile(); + } + } else { + sLogger.info("Running as an plain JUnit test, using url as-is"); + mOsRootDataPath = url.getFile(); + } + } + + if (mOsRootDataPath.equals(AdtConstants.WS_SEP)) { + sLogger.warning("Resource data not found using class loader!, Defaulting to no path"); + } + + if (File.separatorChar == '\\') { + // On Windows, uniformize all separators to use the / convention + mOsRootDataPath.replace('\\', DIR_SEP_CHAR); + } + + if (!mOsRootDataPath.endsWith(File.separator) && !mOsRootDataPath.endsWith(DIR_SEP_STR)) { + sLogger.info("Fixing test_data env variable (does not end with path separator)"); + mOsRootDataPath += DIR_SEP_STR; + } + } + + /** Get the singleton instance of AdtTestData */ + public static AdtTestData getInstance() { + if (sInstance == null) { + sInstance = new AdtTestData(); + } + return sInstance; + } + + /** + * Returns the absolute file path to a file located in this plugin. + * + * @param osRelativePath {@link String} path to file contained in plugin. Must + * use path separators appropriate to host OS + * + * @return absolute OS path to test file + */ + public String getTestFilePath(String osRelativePath) { + File path = new File(mOsRootDataPath, osRelativePath); + + if (!path.exists()) { + // On Windows at least this ends up using the wrong plugin path. + String pkgAdt = AdtPlugin .class.getPackage().getName(); + String pkgTests = AdtTestData.class.getPackage().getName(); + + if (mOsRootDataPath.contains(pkgAdt)) { + path = new File(mOsRootDataPath.replace(pkgAdt, pkgTests), osRelativePath); + } + + assert path.exists(); + } + + return path.getAbsolutePath(); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/AllTests.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/AllTests.java new file mode 100644 index 000000000..5386142a8 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/AllTests.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.ide.eclipse.tests; + +import junit.framework.TestSuite; + + +/** + * Container TestSuite for all eclipse tests, both functional and unit + */ +public class AllTests extends TestSuite { + + public AllTests() { + + } + + /** + * Returns a suite of test cases to be run. + */ + public static TestSuite suite() { + TestSuite suite = new TestSuite(); + suite.addTest(FuncTests.suite()); + suite.addTest(UnitTests.suite()); + return suite; + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/EclipseTestCollector.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/EclipseTestCollector.java new file mode 100644 index 000000000..6bbc955ee --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/EclipseTestCollector.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.ide.eclipse.tests; + +import org.eclipse.core.runtime.Plugin; + +import java.lang.reflect.Modifier; +import java.net.URL; +import java.util.Enumeration; + +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Class for collecting all test cases in an eclipse plugin + * + */ +public class EclipseTestCollector { + + /** + * Constructor + */ + public EclipseTestCollector() { + + } + + /** + * Searches through given plugin, adding all TestCase classes to given suite + * @param suite - TestSuite to add to + * @param plugin - Plugin to search for tests + * @param expectedPackage - expected package for tests. Only test classes + * that start with this package name will be added to suite + */ + @SuppressWarnings({"cast", "unchecked"}) + public void addTestCases(TestSuite suite, Plugin plugin, String expectedPackage) { + if (plugin != null) { + Enumeration<?> entries = plugin.getBundle().findEntries("/", "*.class", true); + + while (entries.hasMoreElements()) { + URL entry = (URL)entries.nextElement(); + String filePath = entry.getPath().replace(".class", ""); + try { + Class<?> testClass = getClass(filePath, expectedPackage); + if (isTestClass(testClass)) { + // In Eclipse 3.6 RCP Windows-x64, the signature has changed from + // addTestSuite(Class) + // to: + // addTestSuite(Class<? extends TestCase>) + // which is enough to create an error. To solve it, we cast into the + // generics expected by the JUnit framework used by 3.6 and suppress the + // warnings generated by the compiler under 3.5 + suite.addTestSuite((Class<? extends TestCase>)testClass); + } + } + catch (ClassNotFoundException e) { + // ignore, this is not the class we're looking for + //sLogger.log(Level.INFO, "Could not load class " + filePath); + } + } + } + } + + /** + * Returns true if given class should be added to suite + */ + protected boolean isTestClass(Class<?> testClass) { + return TestCase.class.isAssignableFrom(testClass) && + Modifier.isPublic(testClass.getModifiers()) && + hasPublicConstructor(testClass); + } + + /** + * Returns true if given class has a public constructor + */ + @SuppressWarnings({"unchecked", "cast"}) + protected boolean hasPublicConstructor(Class<?> testClass) { + try { + // In Eclipse 3.6 RCP Windows-x64, the signature has changed from + // getTestConstructor(Class) + // to: + // getTestConstructor(Class<? extends TestCase>) + // which is enough to create an error. To solve it, we cast into the + // generics expected by the JUnit framework used by 3.6 and suppress the + // warnings generated by the compiler under 3.5 + TestSuite.getTestConstructor((Class<? extends TestCase>) testClass); + } catch(NoSuchMethodException e) { + return false; + } + return true; + } + + /** + * Load the class given by the plugin aka bundle file path + * @param filePath - path of class in bundle + * @param expectedPackage - expected package of class + * @throws ClassNotFoundException + */ + protected Class<?> getClass(String filePath, String expectedPackage) throws ClassNotFoundException { + String dotPath = filePath.replace('/', '.'); + // remove the output folders, by finding where package name starts + int index = dotPath.indexOf(expectedPackage); + if (index == -1) { + throw new ClassNotFoundException(); + } + String packagePath = dotPath.substring(index); + return Class.forName(packagePath); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/FuncTests.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/FuncTests.java new file mode 100644 index 000000000..efa880126 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/FuncTests.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.ide.eclipse.tests; + +import com.android.ide.eclipse.tests.functests.layoutRendering.ApiDemosRenderingTest; + +import junit.framework.TestSuite; + +/** + * Container TestSuite for all eclipse tests to be run + */ + +public class FuncTests extends TestSuite { + + static final String FUNC_TEST_PACKAGE = "com.android.ide.eclipse.tests.functests"; + + public FuncTests() { + + } + + /** + * Returns a suite of test cases to be run. + * Needed for JUnit3 compliant command line test runner + */ + public static TestSuite suite() { + TestSuite suite = new TestSuite(); + + // TODO: uncomment this when 'gen' folder error on create is fixed + // suite.addTestSuite(SampleProjectTest.class); + suite.addTestSuite(ApiDemosRenderingTest.class); + + return suite; + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/SdkLoadingTestCase.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/SdkLoadingTestCase.java new file mode 100644 index 000000000..65ce8e1d7 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/SdkLoadingTestCase.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.ide.eclipse.tests; + +import com.android.ide.common.sdk.LoadStatus; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; +import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetParser; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.android.sdklib.IAndroidTarget; +import com.android.testutils.SdkTestCase; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; + +/** + * A test case which uses the SDK loaded by the ADT plugin. + */ +public abstract class SdkLoadingTestCase extends SdkTestCase { + + private Sdk mSdk; + + protected SdkLoadingTestCase() { + } + + /** + * Retrieve the {@link Sdk} under test. + */ + protected Sdk getSdk() { + if (mSdk == null) { + mSdk = loadSdk(); + assertNotNull(mSdk); + validateSdk(mSdk); + } + return mSdk; + } + + /** + * Gets the current SDK from ADT, waiting if necessary. + */ + private Sdk loadSdk() { + AdtPlugin adt = AdtPlugin.getDefault(); + + // We'll never get an AdtPlugin object when running this with the + // non-Eclipse jUnit test runner. + if (adt == null) { + return null; + } + + // We'll never break out of the SDK load-wait-loop if the AdtPlugin doesn't + // actually have a valid SDK location because it won't have started an async load: + String sdkLocation = AdtPrefs.getPrefs().getOsSdkFolder(); + assertTrue("No valid SDK installation is set; for tests you typically need to set the" + + " environment variable ADT_TEST_SDK_PATH to point to an SDK folder", + sdkLocation != null && sdkLocation.length() > 0); + + Object sdkLock = Sdk.getLock(); + LoadStatus loadStatus = LoadStatus.LOADING; + // wait for ADT to load the SDK on a separate thread + // loop max of 600 times * 200 ms = 2 minutes + final int maxWait = 600; + for (int i=0; i < maxWait && loadStatus == LoadStatus.LOADING; i++) { + try { + Thread.sleep(200); + } + catch (InterruptedException e) { + // ignore + } + synchronized (sdkLock) { + loadStatus = adt.getSdkLoadStatus(); + } + } + Sdk sdk = null; + synchronized (sdkLock) { + assertEquals(LoadStatus.LOADED, loadStatus); + sdk = Sdk.getCurrent(); + } + assertNotNull(sdk); + return sdk; + } + + protected boolean validateSdk(IAndroidTarget target) { + return true; + } + + /** + * Checks that the provided sdk contains one or more valid targets. + * @param sdk the {@link Sdk} to validate. + */ + private void validateSdk(Sdk sdk) { + assertTrue("sdk has no targets", sdk.getTargets().length > 0); + for (IAndroidTarget target : sdk.getTargets()) { + if (!validateSdk(target)) { + continue; + } + if (false) { // This takes forEVER + IStatus status = new AndroidTargetParser(target).run(new NullProgressMonitor()); + if (status.getCode() != IStatus.OK) { + fail("Failed to parse targets data"); + } + } + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/UnitTests.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/UnitTests.java new file mode 100644 index 000000000..15d38715c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/UnitTests.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.ide.eclipse.tests; + + +import com.android.ide.eclipse.adt.AdtPlugin; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * Container TestSuite for all eclipse unit tests to be run + * + * Uses Eclipse OSGI to find and then run all junit.junit.framework.Tests in + * this plugin, excluding tests in the FuncTests.FUNC_TEST_PACKAGE package + * + * Since it uses Eclipse OSGI, it must be run in a Eclipse plugin environment + * i.e. from Eclipse workbench, this suite must be run using the + * "JUnit Plug-in Test" launch configuration as opposed to as a "JUnit Test" + * + */ +public class UnitTests { + private static final String TEST_PACKAGE = "com.android"; + + public static Test suite() { + TestSuite suite = new TestSuite(); + + UnitTestCollector collector = new UnitTestCollector(); + // since this plugin is a fragment which runs insde adt, gather tests from AdtPlugin + collector.addTestCases(suite, AdtPlugin.getDefault(), TEST_PACKAGE); + + return suite; + } + + /** + * Specialized test collector which will skip adding functional tests + */ + private static class UnitTestCollector extends EclipseTestCollector { + /** + * Override parent class to exclude functional tests + */ + @Override + protected boolean isTestClass(Class<?> testClass) { + return super.isTestClass(testClass) && + !testClass.getPackage().getName().startsWith(FuncTests.FUNC_TEST_PACKAGE); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/layoutRendering/ApiDemosRenderingTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/layoutRendering/ApiDemosRenderingTest.java new file mode 100644 index 000000000..d6b401b62 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/layoutRendering/ApiDemosRenderingTest.java @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.ide.eclipse.tests.functests.layoutRendering; + +import com.android.SdkConstants; +import com.android.ide.common.rendering.LayoutLibrary; +import com.android.ide.common.rendering.api.ActionBarCallback; +import com.android.ide.common.rendering.api.AdapterBinding; +import com.android.ide.common.rendering.api.HardwareConfig; +import com.android.ide.common.rendering.api.ILayoutPullParser; +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.RenderSession; +import com.android.ide.common.rendering.api.ResourceReference; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.rendering.api.SessionParams; +import com.android.ide.common.rendering.api.SessionParams.RenderingMode; +import com.android.ide.common.resources.ResourceItem; +import com.android.ide.common.resources.ResourceRepository; +import com.android.ide.common.resources.ResourceResolver; +import com.android.ide.common.resources.configuration.DensityQualifier; +import com.android.ide.common.resources.configuration.FolderConfiguration; +import com.android.ide.common.resources.configuration.KeyboardStateQualifier; +import com.android.ide.common.resources.configuration.NavigationMethodQualifier; +import com.android.ide.common.resources.configuration.NavigationStateQualifier; +import com.android.ide.common.resources.configuration.ScreenDimensionQualifier; +import com.android.ide.common.resources.configuration.ScreenHeightQualifier; +import com.android.ide.common.resources.configuration.ScreenOrientationQualifier; +import com.android.ide.common.resources.configuration.ScreenRatioQualifier; +import com.android.ide.common.resources.configuration.ScreenSizeQualifier; +import com.android.ide.common.resources.configuration.ScreenWidthQualifier; +import com.android.ide.common.resources.configuration.SmallestScreenWidthQualifier; +import com.android.ide.common.resources.configuration.TextInputMethodQualifier; +import com.android.ide.common.resources.configuration.TouchScreenQualifier; +import com.android.ide.common.sdk.LoadStatus; +import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; +import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; +import com.android.ide.eclipse.tests.SdkLoadingTestCase; +import com.android.io.FolderWrapper; +import com.android.resources.Density; +import com.android.resources.Keyboard; +import com.android.resources.KeyboardState; +import com.android.resources.Navigation; +import com.android.resources.NavigationState; +import com.android.resources.ResourceType; +import com.android.resources.ScreenOrientation; +import com.android.resources.ScreenRatio; +import com.android.resources.ScreenSize; +import com.android.resources.TouchScreen; +import com.android.sdklib.IAndroidTarget; +import com.android.util.Pair; + +import org.kxml2.io.KXmlParser; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import javax.imageio.ImageIO; + +public class ApiDemosRenderingTest extends SdkLoadingTestCase { + + /** + * Custom parser that implements {@link ILayoutPullParser} (which itself extends + * {@link XmlPullParser}). + */ + private final static class TestParser extends KXmlParser implements ILayoutPullParser { + /** + * Since we're not going to go through the result of the rendering/layout, we can return + * null for the View Key. + */ + @Override + public Object getViewCookie() { + return null; + } + + @Override + public ILayoutPullParser getParser(String layoutName) { + return null; + } + } + + private final static class ProjectCallBack implements IProjectCallback { + // resource id counter. + // We start at 0x7f000000 to avoid colliding with the framework id + // since we have no access to the project R.java and we need to generate them automatically. + private int mIdCounter = 0x7f000000; + + // in some cases, the id that getResourceValue(String type, String name) returns + // will be sent back to get the type/name. This map stores the id/type/name we generate + // to be able to do the reverse resolution. + private Map<Integer, Pair<ResourceType, String>> mResourceMap = + new HashMap<Integer, Pair<ResourceType, String>>(); + + private boolean mCustomViewAttempt = false; + + @Override + public String getNamespace() { + // TODO: read from the ApiDemos manifest. + return "com.example.android.apis"; + } + + @Override + @SuppressWarnings("unchecked") + public Object loadView(String name, Class[] constructorSignature, Object[] constructorArgs) + throws ClassNotFoundException, Exception { + mCustomViewAttempt = true; + return null; + } + + @Override + public Integer getResourceId(ResourceType type, String name) { + Integer result = ++mIdCounter; + mResourceMap.put(result, Pair.of(type, name)); + return result; + } + + @Override + public Pair<ResourceType, String> resolveResourceId(int id) { + return mResourceMap.get(id); + } + + @Override + public String resolveResourceId(int[] id) { + return null; + } + + @Override + public ILayoutPullParser getParser(String layoutName) { + return null; + } + + @Override + public Object getAdapterItemValue(ResourceReference adapterView, Object adapterCookie, + ResourceReference itemRef, int fullPosition, int typePosition, + int fullChildPosition, int typeChildPosition, + ResourceReference viewRef, ViewAttribute viewAttribute, Object defaultValue) { + return null; + } + + @Override + public AdapterBinding getAdapterBinding(ResourceReference adapterView, + Object adapterCookie, Object viewObject) { + return null; + } + + @Override + public ILayoutPullParser getParser(ResourceValue layoutResource) { + return null; + } + + @Override + public ActionBarCallback getActionBarCallback() { + return new ActionBarCallback(); + } + } + + public void testApiDemos() throws IOException, XmlPullParserException { + findApiDemos(); + } + + private void findApiDemos() throws IOException, XmlPullParserException { + IAndroidTarget[] targets = getSdk().getTargets(); + + for (IAndroidTarget target : targets) { + String path = target.getPath(IAndroidTarget.SAMPLES); + File samples = new File(path); + if (samples.isDirectory()) { + File[] files = samples.listFiles(); + for (File file : files) { + if ("apidemos".equalsIgnoreCase(file.getName())) { + testSample(target, file); + return; + } + } + } + } + + fail("Failed to find ApiDemos!"); + } + + private void testSample(IAndroidTarget target, File sampleProject) throws IOException, XmlPullParserException { + AndroidTargetData data = getSdk().getTargetData(target); + if (data == null) { + fail("No AndroidData!"); + } + + LayoutLibrary layoutLib = data.getLayoutLibrary(); + if (layoutLib.getStatus() != LoadStatus.LOADED) { + fail("Fail to load the bridge: " + layoutLib.getLoadMessage()); + } + + FolderWrapper resFolder = new FolderWrapper(sampleProject, SdkConstants.FD_RES); + if (resFolder.exists() == false) { + fail("Sample project has no res folder!"); + } + + // look for the layout folder + File layoutFolder = new File(resFolder, SdkConstants.FD_RES_LAYOUT); + if (layoutFolder.isDirectory() == false) { + fail("Sample project has no layout folder!"); + } + + // first load the project's target framework resource + ResourceRepository framework = ResourceManager.getInstance().loadFrameworkResources(target); + + // now load the project resources + ResourceRepository project = new ResourceRepository(resFolder, false) { + @Override + protected ResourceItem createResourceItem(String name) { + return new ResourceItem(name); + } + + }; + + // Create a folder configuration that will be used for the rendering: + FolderConfiguration config = getConfiguration(); + + // get the configured resources + Map<ResourceType, Map<String, ResourceValue>> configuredFramework = + framework.getConfiguredResources(config); + Map<ResourceType, Map<String, ResourceValue>> configuredProject = + project.getConfiguredResources(config); + + boolean saveFiles = System.getenv("save_file") != null; + + // loop on the layouts and render them + File[] layouts = layoutFolder.listFiles(); + for (File layout : layouts) { + // create a parser for the layout file + TestParser parser = new TestParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + parser.setInput(new FileReader(layout)); + + System.out.println("Rendering " + layout.getName()); + + ProjectCallBack projectCallBack = new ProjectCallBack(); + + ResourceResolver resolver = ResourceResolver.create( + configuredProject, configuredFramework, + "Theme", false /*isProjectTheme*/); + + HardwareConfig hardwareConfig = new HardwareConfig( + 320, + 480, + Density.MEDIUM, + 160, //xdpi + 160, // ydpi + ScreenSize.NORMAL, + ScreenOrientation.PORTRAIT, + false /*software buttons */); + + RenderSession session = layoutLib.createSession(new SessionParams( + parser, + RenderingMode.NORMAL, + null /*projectKey*/, + hardwareConfig, + resolver, + projectCallBack, + 1, // minSdkVersion + 1, // targetSdkVersion + null //logger + )); + + if (session.getResult().isSuccess() == false) { + if (projectCallBack.mCustomViewAttempt == false) { + System.out.println("FAILED"); + fail(String.format("Rendering %1$s: %2$s", layout.getName(), + session.getResult().getErrorMessage())); + } else { + System.out.println("Ignore custom views for now"); + } + } else { + if (saveFiles) { + File tmp = File.createTempFile(layout.getName(), ".png"); + ImageIO.write(session.getImage(), "png", tmp); + } + System.out.println("Success!"); + } + } + } + + /** + * Returns a config. This must be a valid config like a device would return. This is to + * prevent issues where some resources don't exist in all cases and not in the default + * (for instance only available in hdpi and mdpi but not in default). + * @return + */ + private FolderConfiguration getConfiguration() { + FolderConfiguration config = new FolderConfiguration(); + + // this matches an ADP1. + config.addQualifier(new SmallestScreenWidthQualifier(320)); + config.addQualifier(new ScreenWidthQualifier(320)); + config.addQualifier(new ScreenHeightQualifier(480)); + config.addQualifier(new ScreenSizeQualifier(ScreenSize.NORMAL)); + config.addQualifier(new ScreenRatioQualifier(ScreenRatio.NOTLONG)); + config.addQualifier(new ScreenOrientationQualifier(ScreenOrientation.PORTRAIT)); + config.addQualifier(new DensityQualifier(Density.MEDIUM)); + config.addQualifier(new TouchScreenQualifier(TouchScreen.FINGER)); + config.addQualifier(new KeyboardStateQualifier(KeyboardState.HIDDEN)); + config.addQualifier(new TextInputMethodQualifier(Keyboard.QWERTY)); + config.addQualifier(new NavigationStateQualifier(NavigationState.HIDDEN)); + config.addQualifier(new NavigationMethodQualifier(Navigation.TRACKBALL)); + config.addQualifier(new ScreenDimensionQualifier(480, 320)); + + config.updateScreenWidthAndHeight(); + + return config; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/sampleProjects/AndroidManifestWriter.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/sampleProjects/AndroidManifestWriter.java new file mode 100644 index 000000000..141e7e000 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/sampleProjects/AndroidManifestWriter.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.ide.eclipse.tests.functests.sampleProjects; + +import com.android.SdkConstants; +import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; +import com.android.xml.AndroidManifest; + +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import java.io.File; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +/** + * Helper class for modifying an AndroidManifest. + * <p/> + * TODO: consider merging this with AndroidManifestParser. + */ +class AndroidManifestWriter { + + private static final Logger sLogger = Logger.getLogger(AndroidManifestWriter.class.getName()); + + private final Document mDoc; + private final String mOsManifestFilePath; + + private AndroidManifestWriter(Document doc, String osManifestFilePath) { + mDoc = doc; + mOsManifestFilePath = osManifestFilePath; + } + + /** + * Sets the minimum SDK version for this manifest + * @param minSdkVersion - the minimim sdk version to use + * @returns <code>true</code> on success, false otherwise + */ + public boolean setMinSdkVersion(String minSdkVersion) { + Element usesSdkElement = null; + NodeList nodeList = mDoc.getElementsByTagName(AndroidManifest.NODE_USES_SDK); + if (nodeList.getLength() > 0) { + usesSdkElement = (Element) nodeList.item(0); + } else { + usesSdkElement = mDoc.createElement(AndroidManifest.NODE_USES_SDK); + mDoc.getDocumentElement().appendChild(usesSdkElement); + } + Attr minSdkAttr = mDoc.createAttributeNS(SdkConstants.NS_RESOURCES, + AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION); + String prefix = mDoc.lookupPrefix(SdkConstants.NS_RESOURCES); + minSdkAttr.setPrefix(prefix); + minSdkAttr.setValue(minSdkVersion); + usesSdkElement.setAttributeNodeNS(minSdkAttr); + return saveXmlToFile(); + } + + private boolean saveXmlToFile() { + try { + // Prepare the DOM document for writing + Source source = new DOMSource(mDoc); + + // Prepare the output file + File file = new File(mOsManifestFilePath); + Result result = new StreamResult(file); + + // Write the DOM document to the file + Transformer xformer = TransformerFactory.newInstance().newTransformer(); + xformer.transform(source, result); + } catch (TransformerConfigurationException e) { + sLogger.log(Level.SEVERE, "Failed to write xml file", e); + return false; + } catch (TransformerException e) { + sLogger.log(Level.SEVERE, "Failed to write xml file", e); + return false; + } + return true; + } + + /** + * Parses the manifest file, and collects data. + * + * @param osManifestFilePath The OS path of the manifest file to parse. + * @return an {@link AndroidManifestHelper} or null if parsing failed + */ + public static AndroidManifestWriter parse(String osManifestFilePath) { + try { + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + docFactory.setNamespaceAware(true); + DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + Document doc = docBuilder.parse(osManifestFilePath); + return new AndroidManifestWriter(doc, osManifestFilePath); + } catch (ParserConfigurationException e) { + sLogger.log(Level.SEVERE, "Error parsing file", e); + return null; + } catch (SAXException e) { + sLogger.log(Level.SEVERE, "Error parsing file", e); + return null; + } catch (IOException e) { + sLogger.log(Level.SEVERE, "Error parsing file", e); + return null; + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/sampleProjects/SampleProjectTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/sampleProjects/SampleProjectTest.java new file mode 100644 index 000000000..3fb705dfb --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/sampleProjects/SampleProjectTest.java @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.ide.eclipse.tests.functests.sampleProjects; + +import com.android.SdkConstants; +import com.android.ide.eclipse.adt.AdtUtils; +import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectCreator; +import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState; +import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode; +import com.android.ide.eclipse.tests.SdkLoadingTestCase; +import com.android.sdklib.IAndroidTarget; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceChangeEvent; +import org.eclipse.core.resources.IResourceChangeListener; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.resources.IResourceDeltaVisitor; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jface.operation.IRunnableContext; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.swt.widgets.Display; + +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Test case that verifies all SDK sample projects can be imported, and built in + * Eclipse. + * <p/> + * TODO: add support for deploying apps onto emulator and verifying successful + * execution there + * + */ +public class SampleProjectTest extends SdkLoadingTestCase { + + private static final Logger sLogger = Logger.getLogger(SampleProjectTest.class.getName()); + + /** + * Finds all samples projects in set SDK and verify they can be built in Eclipse. + * <p/> + * TODO: add install and run on emulator test + * @throws CoreException + */ + public void testSamples() throws CoreException { + // TODO: For reporting purposes, it would be better if a separate test success or failure + // could be reported for each sample + IAndroidTarget[] targets = getSdk().getTargets(); + for (IAndroidTarget target : targets) { + doTestSamplesForTarget(target); + } + } + + private void doTestSamplesForTarget(IAndroidTarget target) throws CoreException { + String path = target.getPath(IAndroidTarget.SAMPLES); + File samples = new File(path); + if (samples.isDirectory()) { + File[] files = samples.listFiles(); + for (File file : files) { + if (file.isDirectory()) { + doTestSampleProject(file.getName(), file.getAbsolutePath(), target); + } + } + } + } + + /** + * Tests the sample project with the given name + * + * @param target - SDK target of project + * @param name - name of sample project to test + * @param path - absolute file system path + * @throws CoreException + */ + private void doTestSampleProject(String name, String path, IAndroidTarget target) + throws CoreException { + IProject iproject = null; + try { + sLogger.log(Level.INFO, String.format("Testing sample %s for target %s", name, + target.getName())); + + prepareProject(path, target); + + IRunnableContext context = new IRunnableContext() { + @Override + public void run(boolean fork, boolean cancelable, IRunnableWithProgress runnable) + throws InvocationTargetException, InterruptedException { + runnable.run(new NullProgressMonitor()); + } + }; + NewProjectWizardState state = new NewProjectWizardState(Mode.SAMPLE); + state.projectName = name; + state.target = target; + state.packageName = "com.android.samples"; + state.activityName = name; + state.applicationName = name; + state.chosenSample = new File(path); + state.useDefaultLocation = false; + state.createActivity = false; + + NewProjectCreator creator = new NewProjectCreator(state, context); + creator.createAndroidProjects(); + iproject = validateProjectExists(name); + validateNoProblems(iproject); + } + catch (CoreException e) { + sLogger.log(Level.SEVERE, + String.format("Unexpected exception when creating sample project %s " + + "for target %s", name, target.getName())); + throw e; + } finally { + if (iproject != null) { + iproject.delete(false, true, new NullProgressMonitor()); + } + } + } + + private void prepareProject(String path, IAndroidTarget target) { + if (target.getVersion().isPreview()) { + // need to explicitly set preview's version in manifest for project to compile + final String manifestPath = path + File.separatorChar + + SdkConstants.FN_ANDROID_MANIFEST_XML; + AndroidManifestWriter manifestWriter = + AndroidManifestWriter.parse(manifestPath); + assertNotNull(String.format("could not read manifest %s", manifestPath), + manifestWriter); + assertTrue(manifestWriter.setMinSdkVersion(target.getVersion().getApiString())); + } + } + + private IProject validateProjectExists(String name) { + IProject iproject = getIProject(name); + assertTrue(String.format("%s project not created", name), iproject.exists()); + assertTrue(String.format("%s project not opened", name), iproject.isOpen()); + return iproject; + } + + private IProject getIProject(String name) { + IProject iproject = ResourcesPlugin.getWorkspace().getRoot() + .getProject(name); + return iproject; + } + + private void validateNoProblems(IProject iproject) throws CoreException { + waitForBuild(iproject); + + boolean hasErrors = false; + StringBuilder failureBuilder = new StringBuilder(String.format("%s project has errors:", + iproject.getName())); + IMarker[] markers = iproject.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE); + if (markers != null && markers.length > 0) { + // the project has marker(s). even though they are "problem" we + // don't know their severity. so we loop on them and figure if they + // are warnings or errors + for (IMarker m : markers) { + int s = m.getAttribute(IMarker.SEVERITY, -1); + if (s == IMarker.SEVERITY_ERROR) { + hasErrors = true; + failureBuilder.append("\n"); + failureBuilder.append(m.getAttribute(IMarker.MESSAGE, "")); + } + } + } + failureBuilder.append("Project location: " + AdtUtils.getAbsolutePath(iproject)); + assertFalse(failureBuilder.toString(), hasErrors); + } + + /** + * Waits for build to complete. + * + * @param iproject + */ + private void waitForBuild(final IProject iproject) { + + final BuiltProjectDeltaVisitor deltaVisitor = new BuiltProjectDeltaVisitor(iproject); + IResourceChangeListener newBuildListener = new IResourceChangeListener() { + + @Override + public void resourceChanged(IResourceChangeEvent event) { + try { + event.getDelta().accept(deltaVisitor); + } + catch (CoreException e) { + fail(); + } + } + + }; + iproject.getWorkspace().addResourceChangeListener(newBuildListener, + IResourceChangeEvent.POST_BUILD); + + // poll build listener to determine when build is done + // loop max of 1200 times * 50 ms = 60 seconds + final int maxWait = 1200; + for (int i=0; i < maxWait; i++) { + if (deltaVisitor.isProjectBuilt()) { + return; + } + try { + Thread.sleep(50); + } + catch (InterruptedException e) { + // ignore + } + if (Display.getCurrent() != null) { + Display.getCurrent().readAndDispatch(); + } + } + + sLogger.log(Level.SEVERE, "expected build event never happened?"); + fail(String.format("Expected build event never happened for %s", iproject.getName())); + } + + /** + * Scans a given IResourceDelta looking for a "build event" change for given IProject + * + */ + private class BuiltProjectDeltaVisitor implements IResourceDeltaVisitor { + + private IProject mIProject; + private boolean mIsBuilt; + + public BuiltProjectDeltaVisitor(IProject iproject) { + mIProject = iproject; + mIsBuilt = false; + } + + @Override + public boolean visit(IResourceDelta delta) { + if (mIProject.equals(delta.getResource())) { + setBuilt(true); + return false; + } + return true; + } + + private synchronized void setBuilt(boolean b) { + mIsBuilt = b; + } + + public synchronized boolean isProjectBuilt() { + return mIsBuilt; + } + } +} |