/* * Copyright (C) 2013 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.idea.templates; import com.android.SdkConstants; import com.android.annotations.NonNull; import com.android.sdklib.SdkVersionInfo; import com.android.sdklib.BuildToolInfo; import com.android.sdklib.IAndroidTarget; import com.android.tools.idea.sdk.VersionCheck; import com.android.tools.idea.wizard.ConfigureAndroidModuleStep; import com.android.tools.idea.wizard.NewProjectWizardState; import com.android.tools.idea.wizard.StringEvaluator; import com.android.tools.idea.wizard.TemplateWizardState; import com.android.tools.lint.checks.ManifestDetector; import com.android.tools.lint.detector.api.Severity; import com.google.common.base.Stopwatch; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ex.ProjectManagerEx; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.testFramework.fixtures.*; import org.jetbrains.android.sdk.AndroidSdkData; import org.jetbrains.android.sdk.AndroidSdkUtils; import org.jetbrains.annotations.Nullable; import org.w3c.dom.Element; import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import static com.android.SdkConstants.*; import static com.android.tools.idea.templates.TemplateMetadata.*; import static com.android.tools.idea.templates.TemplateMetadata.ATTR_TARGET_API; import static com.android.tools.idea.wizard.NewModuleWizardState.ATTR_CREATE_ACTIVITY; import static com.android.tools.idea.wizard.NewModuleWizardState.ATTR_PROJECT_LOCATION; import static com.android.tools.idea.wizard.FormFactorUtils.ATTR_MODULE_NAME; /** * Test for template instantiation. * <p> * Remaining work on templates: * <ul> * <li>Make the project templates work for API=1 (currently requires API 7); with API 1 * you get a build error because included libraries have targetSdkVersion higher than 1</li> * <li>Fix type conversion, to make the service and fragment templates work</li> * <li>Fix compilation errors in the remaining templates</li> * <li>Should abstract out the state initialization code from the UI such that we * can use the same code path from the test</li> * </ul> * * Remaining work on template test: * <ul> * <li>Fix clean model syncing, and hook up clean lint checks</li> * <li>We should test more combinations of parameters</li> * <li>We should test all combinations of build tools</li> * <li>Test creating a project <b>without</b> a template</li> * </ul> */ public class TemplateTest extends AndroidGradleTestCase { /** * Whether we should run comprehensive tests or not. This flag allows a simple run to just check a small set of * template combinations, and when the flag is set on the build server, a much more comprehensive battery of * checks to be performed. */ private static final boolean COMPREHENSIVE = Boolean.parseBoolean(System.getProperty("com.android.tools.idea.templates.TemplateTest.COMPREHENSIVE")) || Boolean.TRUE.toString().equals(System.getenv("com.android.tools.idea.templates.TemplateTest.COMPREHENSIVE")); /** * Whether we should run these tests or not. */ private static final boolean DISABLED = Boolean.parseBoolean(System.getProperty("DISABLE_STUDIO_TEMPLATE_TESTS")) || Boolean.TRUE.toString().equals(System.getenv("DISABLE_STUDIO_TEMPLATE_TESTS")); /** Whether we should enforce that lint passes cleanly on the projects */ private static final boolean CHECK_LINT = false; // Needs work on closing projects cleanly private static final boolean ALLOW_WARNINGS = true; // TODO: Provide finer granularity /** * The following templates are known to be broken! We need to work through these and fix them such that tests * on them can be re-enabled. */ private static final Set<String> KNOWN_BROKEN = Sets.newHashSet(); static { } /** * Flags used to quickly check each template once (for one version), to get * quicker feedback on whether something is broken instead of waiting for * all the versions for each template first */ private static final boolean TEST_VARIABLE_COMBINATIONS = true; private static final boolean TEST_FEWER_API_VERSIONS = !COMPREHENSIVE; private static final boolean TEST_JUST_ONE_MIN_SDK = !COMPREHENSIVE; private static final boolean TEST_JUST_ONE_BUILD_TARGET = !COMPREHENSIVE; private static final boolean TEST_JUST_ONE_TARGET_SDK_VERSION = !COMPREHENSIVE; private static int ourCount = 0; private static boolean ourValidatedTemplateManager; private final StringEvaluator myStringEvaluator = new StringEvaluator(); public TemplateTest() { } @Override protected boolean createDefaultProject() { // We'll be creating projects manually //return false; return true; // Doesn't work yet; find out why! } @Override public void setUp() throws Exception { super.setUp(); myApiSensitiveTemplate = true; if (!ourValidatedTemplateManager) { ourValidatedTemplateManager = true; File templateRootFolder = TemplateManager.getTemplateRootFolder(); if (templateRootFolder == null) { AndroidSdkData sdkData = AndroidSdkUtils.tryToChooseAndroidSdk(); if (sdkData == null) { fail("Couldn't find SDK manager"); } else { System.out.println("recentSDK required= " + requireRecentSdk()); System.out.println("getTestSdkPath= " + getTestSdkPath()); System.out.println("getPlatformDir=" + getPlatformDir()); String location = sdkData.getLocation().getPath(); System.out.println("Using SDK at " + location); VersionCheck.VersionCheckResult result = VersionCheck.checkVersion(location); System.out.println("Version check=" + result.getRevision()); File file = new File(location); if (!file.exists()) { System.out.println("SDK doesn't exist"); } else { File folder = new File(location, FD_TOOLS + File.separator + FD_TEMPLATES); boolean exists = folder.exists(); System.out.println("Template folder exists=" + exists + " for " + folder); } } } } } /** * If true, check this template with all the interesting ( * {@link #isInterestingApiLevel(int)}) api versions */ private boolean myApiSensitiveTemplate; /** * Set of templates already tested with separate unit test; remainder is * checked in {@link #testCreateRemainingTemplates()} */ private static final Set<String> ourTemplatesChecked = Sets.newHashSet(); /** * Is the given api level interesting for testing purposes? This is used to * skip gaps, such that we for example only check say api 8, 9, 11, 14, etc * -- versions where the <b>templates</b> are doing conditional changes. To * be EXTRA comprehensive, occasionally try returning true unconditionally * here to test absolutely everything. */ private boolean isInterestingApiLevel(int api) { // For templates that aren't API sensitive, only test with API = 16 if (!myApiSensitiveTemplate) { return api == 16; } // Relevant versions, used to prune down the set of targets we need to // check on. This is determined by looking at the minApi and minBuildApi // versions found in the template.xml files. switch (api) { case 1: case 7: case 11: case 14: case 16: return true; case 9: case 13: case 8: case 3: return !TEST_FEWER_API_VERSIONS; default: return false; } } public void testNewBlankActivity() throws Exception { checkCreateTemplate("activities", "BlankActivity", false); } public void testNewProjectWithBlankActivity() throws Exception { checkCreateTemplate("activities", "BlankActivity", true); } public void testNewTabbedActivity() throws Exception { checkCreateTemplate("activities", "TabbedActivity", false); } public void testNewProjectWithTabbedActivity() throws Exception { checkCreateTemplate("activities", "TabbedActivity", true); } public void testNewNavigationDrawerActivity() throws Exception { checkCreateTemplate("activities", "NavigationDrawerActivity", false); } public void testNewProjectWithNavigationDrawerActivity() throws Exception { checkCreateTemplate("activities", "NavigationDrawerActivity", true); } public void testNewBlankActivityWithFragment() throws Exception { checkCreateTemplate("activities", "BlankActivityWithFragment", false); } public void testNewProjectWithBlankActivityWithFragment() throws Exception { checkCreateTemplate("activities", "BlankActivityWithFragment", true); } public void testNewMasterDetailFlow() throws Exception { checkCreateTemplate("activities", "MasterDetailFlow", false); } public void testNewProjectWithMasterDetailFlow() throws Exception { checkCreateTemplate("activities", "MasterDetailFlow", true); } public void testNewFullscreenActivity() throws Exception { checkCreateTemplate("activities", "FullscreenActivity", false); } public void testNewProjectWithFullscreenActivity() throws Exception { checkCreateTemplate("activities", "FullscreenActivity", true); } public void testNewLoginActivity() throws Exception { checkCreateTemplate("activities", "LoginActivity", false); } public void testNewProjectWithLoginActivity() throws Exception { checkCreateTemplate("activities", "LoginActivity", true); } public void testNewSettingsActivity() throws Exception { checkCreateTemplate("activities", "SettingsActivity", false); } public void testNewProjectWithSettingsActivity() throws Exception { checkCreateTemplate("activities", "SettingsActivity", true); } // Non-activities public void testNewBroadcastReceiver() throws Exception { // No need to try this template with multiple platforms, one is adequate myApiSensitiveTemplate = false; checkCreateTemplate("other", "BroadcastReceiver"); } public void testNewContentProvider() throws Exception { myApiSensitiveTemplate = false; checkCreateTemplate("other", "ContentProvider"); } public void testNewCustomView() throws Exception { myApiSensitiveTemplate = false; checkCreateTemplate("other", "CustomView"); } public void testNewIntentService() throws Exception { myApiSensitiveTemplate = false; checkCreateTemplate("other", "IntentService"); } public void testNewNotification() throws Exception { myApiSensitiveTemplate = false; checkCreateTemplate("other", "Notification"); } public void testNewDayDream() throws Exception { myApiSensitiveTemplate = false; checkCreateTemplate("other", "Daydream"); } public void testNewListFragment() throws Exception { myApiSensitiveTemplate = true; checkCreateTemplate("other", "ListFragment"); } public void testNewAppWidget() throws Exception { myApiSensitiveTemplate = false; checkCreateTemplate("other", "AppWidget"); } public void testNewBlankFragment() throws Exception { myApiSensitiveTemplate = false; checkCreateTemplate("other", "BlankFragment"); } public void testNewService() throws Exception { myApiSensitiveTemplate = false; checkCreateTemplate("other", "Service"); } public void testNewPlusOneFragment() throws Exception { myApiSensitiveTemplate = false; checkCreateTemplate("other", "PlusOneFragment"); } public void testNewAidlFile() throws Exception { myApiSensitiveTemplate = false; checkCreateTemplate("other", "AidlFile"); } public void testNewLayoutResourceFile() throws Exception { myApiSensitiveTemplate = false; checkCreateTemplate("other", "LayoutResourceFile"); } public void testNewValueResourceFile() throws Exception { myApiSensitiveTemplate = false; checkCreateTemplate("other", "ValueResourceFile"); } public void testCreateRemainingTemplates() throws Exception { if (DISABLED) { return; } ourCount = 0; long begin = System.currentTimeMillis(); TemplateManager manager = TemplateManager.getInstance(); List<File> other = manager.getTemplates("other"); for (File templateFile : other) { if (!haveChecked(templateFile, false)) { checkTemplate(templateFile, false); } } // Also try creating templates, not as part of creating a project List<File> activities = manager.getTemplates("activities"); for (File templateFile : activities) { if (!haveChecked(templateFile, false)) { checkTemplate(templateFile, false); } if (!haveChecked(templateFile, true)) { checkTemplate(templateFile, true); } } long end = System.currentTimeMillis(); System.out.println("Successfully checked " + ourCount + " template permutations in " + ((end - begin) / (1000 * 60)) + " minutes"); } public void testJdk7() throws Exception { if (DISABLED) { return; } AndroidSdkData sdkData = AndroidSdkUtils.tryToChooseAndroidSdk(); assertNotNull(sdkData); if (ConfigureAndroidModuleStep.isJdk7Supported(sdkData)) { IAndroidTarget[] targets = sdkData.getTargets(); IAndroidTarget target = targets[targets.length - 1]; Map<String,Object> overrides = Maps.newHashMap(); overrides.put(ATTR_JAVA_VERSION, "1.7"); NewProjectWizardState state = createNewProjectState(true, sdkData); // TODO: Allow null activity state! File activity = findTemplate("activities", "BlankActivity"); TemplateWizardState activityState = state.getActivityTemplateState(); assertNotNull(activity); activityState.setTemplateLocation(activity); checkApiTarget(19, 19, target, state, "Test17", null, overrides); } else { System.out.println("JDK 7 not supported by current SDK manager: not testing"); } } public void testJdk5() throws Exception { if (DISABLED) { return; } AndroidSdkData sdkData = AndroidSdkUtils.tryToChooseAndroidSdk(); assertNotNull(sdkData); IAndroidTarget[] targets = sdkData.getTargets(); IAndroidTarget target = targets[targets.length - 1]; Map<String,Object> overrides = Maps.newHashMap(); overrides.put(ATTR_JAVA_VERSION, "1.5"); NewProjectWizardState state = createNewProjectState(true, sdkData); // TODO: Allow null activity state! File activity = findTemplate("activities", "BlankActivity"); TemplateWizardState activityState = state.getActivityTemplateState(); assertNotNull(activity); activityState.setTemplateLocation(activity); checkApiTarget(8, 18, target, state, "Test15", null, overrides); } // ---- Test support code below ---- /** Checks whether we've already checked the given template in a new project or existing project context */ private static boolean haveChecked(String category, String name, boolean createWithProject) { String key = getCheckKey(category, name, createWithProject); return ourTemplatesChecked.contains(key); } /** Checks whether we've already checked the given template in a new project or existing project context */ private static boolean haveChecked(File templateFile, boolean createWithProject) { return haveChecked(templateFile.getParentFile().getName(), templateFile.getName(), createWithProject); } /** Marks that we've already checked the given template in a new project or existing project context */ private static void markChecked(String category, String name, boolean createWithProject) { String key = getCheckKey(category, name, createWithProject); ourTemplatesChecked.add(key); } /** Marks that we've already checked the given template in a new project or existing project context */ private static void markChecked(File templateFile, boolean createWithProject) { markChecked(templateFile.getParentFile().getName(), templateFile.getName(), createWithProject); } /** Checks the given template in the given category, adding it to an existing project */ private void checkCreateTemplate(String category, String name) throws Exception { checkCreateTemplate(category, name, false); } private static String getCheckKey(String category, String name, boolean createWithProject) { return category + ':' + name + ':' + createWithProject; } /** * Checks the given template in the given category * * @param category the template category * @param name the template name * @param createWithProject whether the template should be created as part of creating the project (which should * only be done for activities), or whether it should be added as as a separate template * into an existing project (which is created first, followed by the template) * @throws Exception */ private void checkCreateTemplate(String category, String name, boolean createWithProject) throws Exception { if (DISABLED) { return; } File templateFile = findTemplate(category, name); assertNotNull(templateFile); if (haveChecked(templateFile, createWithProject)) { return; } if (KNOWN_BROKEN.contains(templateFile.getName())) { return; } markChecked(templateFile, createWithProject); Stopwatch stopwatch = Stopwatch.createStarted(); checkTemplate(templateFile, createWithProject); stopwatch.stop(); System.out.println("Checked " + templateFile.getName() + " successfully in " + stopwatch.toString()); } private File findTemplate(String category, String name) { ensureSdkManagerAvailable(); File templateRootFolder = TemplateManager.getTemplateRootFolder(); assertNotNull(templateRootFolder); File file = new File(templateRootFolder, category + File.separator + name); assertTrue(file.getPath(), file.exists()); return file; } private static NewProjectWizardState createNewProjectState(boolean createWithProject, AndroidSdkData sdkData) { final NewProjectWizardState values = new NewProjectWizardState(); assertNotNull(values); Template.convertApisToInt(values.getParameters()); values.put(ATTR_CREATE_ACTIVITY, createWithProject); values.put(ATTR_GRADLE_VERSION, GRADLE_LATEST_VERSION); values.put(ATTR_GRADLE_PLUGIN_VERSION, GRADLE_PLUGIN_RECOMMENDED_VERSION); values.put(ATTR_MODULE_NAME, "TestModule"); values.put(ATTR_PACKAGE_NAME, "test.pkg"); // TODO: Test the icon generator too values.put(ATTR_CREATE_ICONS, false); final BuildToolInfo buildTool = sdkData.getLatestBuildTool(); if (buildTool != null) { values.put(ATTR_BUILD_TOOLS_VERSION, buildTool.getRevision().toString()); } return values; } private void checkTemplate(File templateFile, boolean createWithProject) throws Exception { if (KNOWN_BROKEN.contains(templateFile.getName())) { return; } AndroidSdkData sdkData = AndroidSdkUtils.tryToChooseAndroidSdk(); assertNotNull(sdkData); final NewProjectWizardState values = createNewProjectState(createWithProject, sdkData); String projectNameBase = "TestProject" + templateFile.getName(); TemplateWizardState state = values.getActivityTemplateState(); state.setTemplateLocation(templateFile); // Iterate over all (valid) combinations of build target, minSdk and targetSdk // TODO: Assert that the SDK manager has a minimum set of SDKs installed needed to be certain // the test is comprehensive IAndroidTarget[] targets = sdkData.getTargets(); for (int i = targets.length - 1; i >= 0; i--) { IAndroidTarget target = targets[i]; if (!target.isPlatform()) { continue; } if (!isInterestingApiLevel(target.getVersion().getApiLevel())) { continue; } TemplateMetadata activityMetadata = state.getTemplateMetadata(); TemplateMetadata projectMetadata = values.getTemplateMetadata(); assertNotNull(activityMetadata); assertNotNull(projectMetadata); int lowestSupportedApi = Math.max(projectMetadata.getMinSdk(), activityMetadata.getMinSdk()); for (int minSdk = lowestSupportedApi; minSdk <= SdkVersionInfo.HIGHEST_KNOWN_API; minSdk++) { // Don't bother checking *every* single minSdk, just pick some interesting ones if (!isInterestingApiLevel(minSdk)) { continue; } for (int targetSdk = minSdk; targetSdk <= SdkVersionInfo.HIGHEST_KNOWN_API; targetSdk++) { if (!isInterestingApiLevel(targetSdk)) { continue; } String status = validateTemplate(projectMetadata, minSdk, target.getVersion().getApiLevel()); if (status != null) { continue; } // Also make sure activity is enabled for these versions status = validateTemplate(activityMetadata, minSdk, target.getVersion().getApiLevel()); if (status != null) { continue; } // Iterate over all new new project templates // should I try all options of theme with all platforms? // or just try all platforms, with one setting for each? // doesn't seem like I need to multiply // just pick the best setting that applies instead for each platform Collection<Parameter> parameters = projectMetadata.getParameters(); projectParameters: for (Parameter parameter : parameters) { List<Element> options = parameter.getOptions(); if (parameter.type == Parameter.Type.ENUM) { assertNotNull(parameter.id); for (Element element : options) { Option option = Option.get(element); String optionId = option.id; int optionMinSdk = option.minSdk; int optionMinBuildApi = option.minBuild; if (optionMinSdk <= minSdk && optionMinBuildApi <= target.getVersion().getApiLevel()) { values.put(parameter.id, optionId); if (parameter.id.equals("baseTheme")) { String base = projectNameBase + "_min_" + minSdk + "_target_" + targetSdk + "_build_" + target.getVersion().getApiLevel() + "_theme_" + optionId; checkApiTarget(minSdk, targetSdk, target, values, base, state, null); if (!TEST_VARIABLE_COMBINATIONS) { break projectParameters; } } } } } } if (TEST_JUST_ONE_TARGET_SDK_VERSION) { break; } } if (TEST_JUST_ONE_MIN_SDK) { break; } } if (TEST_JUST_ONE_BUILD_TARGET) { break; } } } /** Checks creating the given project and template for the given SDK versions */ private void checkApiTarget( int minSdk, int targetSdk, @NonNull IAndroidTarget target, @NonNull NewProjectWizardState projectValues, @NonNull String projectNameBase, @Nullable TemplateWizardState templateValues, @Nullable Map<String,Object> overrides) throws Exception { Boolean createActivity = (Boolean)projectValues.get(ATTR_CREATE_ACTIVITY); if (createActivity == null) { createActivity = true; } TemplateWizardState values = createActivity ? projectValues.getActivityTemplateState() : templateValues; assertNotNull(values); projectValues.put(ATTR_MIN_API, Integer.toString(minSdk)); projectValues.put(ATTR_MIN_API_LEVEL, minSdk); projectValues.put(ATTR_TARGET_API, targetSdk); projectValues.put(ATTR_TARGET_API_STRING, Integer.toString(targetSdk)); values.put(ATTR_BUILD_API, target.getVersion().getApiLevel()); values.put(ATTR_BUILD_API_STRING, TemplateMetadata.getBuildApiString(target.getVersion())); assertNotNull(values); // Next check all other parameters, cycling through booleans and enums. Template templateHandler = values.getTemplate(); assertNotNull(templateHandler); TemplateMetadata template = templateHandler.getMetadata(); assertNotNull(template); Collection<Parameter> parameters = template.getParameters(); if (!createActivity) { values.setParameterDefaults(); } if (overrides != null) { for (Map.Entry<String, Object> entry : overrides.entrySet()) { values.put(entry.getKey(), entry.getValue()); } } String projectName; for (Parameter parameter : parameters) { if (parameter.type == Parameter.Type.SEPARATOR || parameter.type == Parameter.Type.STRING) { // TODO: Consider whether we should attempt some strings here continue; } assertNotNull(parameter.id); // The initial (default value); revert to this one after cycling, Object initial = values.get(parameter.id); if (parameter.type == Parameter.Type.ENUM) { List<Element> options = parameter.getOptions(); for (Element element : options) { Option option = Option.get(element); String optionId = option.id; int optionMinSdk = option.minSdk; int optionMinBuildApi = option.minBuild; int projectMinApi = projectValues.getInt(ATTR_MIN_API_LEVEL); int projectBuildApi = projectValues.getInt(ATTR_BUILD_API); if (projectMinApi >= optionMinSdk && projectBuildApi >= optionMinBuildApi) { values.put(parameter.id, optionId); projectName = projectNameBase + "_" + parameter.id + "_" + optionId; checkProject(projectName, projectValues, templateValues); if (!TEST_VARIABLE_COMBINATIONS) { break; } } } } else { assert parameter.type == Parameter.Type.BOOLEAN; if (parameter.id.equals(ATTR_IS_LAUNCHER) && createActivity) { // Skipping this one: always true when launched from new project continue; } boolean value = false; //noinspection ConstantConditions values.put(parameter.id, value); //noinspection ConstantConditions projectName = projectNameBase + "_" + parameter.id + "_" + value; checkProject(projectName, projectValues, templateValues); if (!TEST_VARIABLE_COMBINATIONS) { break; } value = true; //noinspection ConstantConditions values.put(parameter.id, value); //noinspection ConstantConditions projectName = projectNameBase + "_" + parameter.id + "_" + value; checkProject(projectName, projectValues, templateValues); } values.put(parameter.id, initial); } projectName = projectNameBase + "_default"; checkProject(projectName, projectValues, templateValues); } private static class Option { private String id; private int minSdk; private int minBuild; public Option(String id, int minSdk, int minBuild) { this.id = id; this.minSdk = minSdk; this.minBuild = minBuild; } private static Option get(Element option) { String optionId = option.getAttribute(ATTR_ID); String minApiString = option.getAttribute(ATTR_MIN_API); int optionMinSdk = 1; if (minApiString != null && !minApiString.isEmpty()) { try { optionMinSdk = Integer.parseInt(minApiString); } catch (NumberFormatException e) { // Templates aren't allowed to contain codenames, should // always be an integer optionMinSdk = 1; fail(e.toString()); } } String minBuildApiString = option.getAttribute(ATTR_MIN_BUILD_API); int optionMinBuildApi = 1; if (minBuildApiString != null && !minBuildApiString.isEmpty()) { try { optionMinBuildApi = Integer.parseInt(minBuildApiString); } catch (NumberFormatException e) { // Templates aren't allowed to contain codenames, should // always be an integer optionMinBuildApi = 1; fail(e.toString()); } } return new Option(optionId, optionMinSdk, optionMinBuildApi); } } private void checkProject(@NonNull final String projectName, @NonNull NewProjectWizardState projectValues, @Nullable final TemplateWizardState templateValues) throws Exception { ourCount++; projectValues.put(ATTR_RES_OUT, null); projectValues.put(ATTR_SRC_OUT, null); projectValues.put(ATTR_MANIFEST_OUT, null); projectValues.put(ATTR_TEST_OUT, null); JavaCodeInsightTestFixture fixture = null; File projectDir = null; try { projectValues.put(ATTR_MODULE_NAME, projectName); IdeaTestFixtureFactory factory = IdeaTestFixtureFactory.getFixtureFactory(); TestFixtureBuilder<IdeaProjectTestFixture> projectBuilder = factory.createFixtureBuilder(projectName); fixture = JavaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture(projectBuilder.getFixture()); fixture.setUp(); final Project project = fixture.getProject(); projectDir = new File(project.getBasePath()); projectValues.put(ATTR_PROJECT_LOCATION, projectDir.getPath()); // We only need to sync the model if lint needs to look at the synced project afterwards boolean syncModel = CHECK_LINT; //noinspection ConstantConditions createProject(fixture, projectValues, syncModel); if (templateValues != null && !projectValues.getBoolean(ATTR_CREATE_ACTIVITY)) { templateValues.put(ATTR_PROJECT_LOCATION, projectDir.getPath()); ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { File projectRoot = VfsUtilCore.virtualToIoFile(project.getBaseDir()); Template template = templateValues.getTemplate(); assert template != null; File moduleRoot = new File(projectRoot, projectName); templateValues.put(ATTR_MODULE_NAME, moduleRoot.getName()); try { templateValues.populateDirectoryParameters(); } catch (IOException e) { throw new RuntimeException(e); } template.render(moduleRoot, moduleRoot, templateValues.getParameters()); if (Template.ourMostRecentException != null) { fail(Template.ourMostRecentException.getMessage()); } // Add in icons if necessary if (templateValues.getTemplateMetadata() != null && templateValues.getTemplateMetadata().getIconName() != null) { File drawableFolder = new File(FileUtil.join(templateValues.getString(ATTR_RES_OUT)), FileUtil.join("drawable")); drawableFolder.mkdirs(); String fileName = myStringEvaluator.evaluate(templateValues.getTemplateMetadata().getIconName(), templateValues.getParameters()); File iconFile = new File(drawableFolder, fileName + SdkConstants.DOT_XML); File sourceFile = new File(getTestDataPath(), FileUtil.join("drawables", "progress_horizontal.xml")); try { FileUtil.copy(sourceFile, iconFile); } catch (IOException e) { fail(e.getMessage()); } } } }); } assertNotNull(project); System.out.println("Checking project " + projectName + " in " + project.getBaseDir()); assertBuildsCleanly(project, ALLOW_WARNINGS); if (CHECK_LINT) { assertLintsCleanly(project, Severity.INFORMATIONAL, Sets.newHashSet(ManifestDetector.TARGET_NEWER)); // TODO: Check for other warnings / inspections, such as unused imports? } } finally { if (fixture != null) { fixture.tearDown(); } Project[] openProjects = ProjectManagerEx.getInstanceEx().getOpenProjects(); assertTrue(openProjects.length <= 1); // 1: the project created by default by the test case // Clean up; ensure that we don't bleed contents through to the next iteration if (projectDir != null && projectDir.exists()) { FileUtil.delete(projectDir); LocalFileSystem.getInstance().refreshAndFindFileByIoFile(projectDir); } } } /** * Validates this template to make sure it's supported * * @param currentMinSdk the minimum SDK in the project, or -1 or 0 if unknown (e.g. codename) * @param buildApi the build API, or -1 or 0 if unknown (e.g. codename) * * @return an error message, or null if there is no problem */ @Nullable private static String validateTemplate(TemplateMetadata metadata, int currentMinSdk, int buildApi) { if (!metadata.isSupported()) { return "This template requires a more recent version of Android Studio. Please update."; } int templateMinSdk = metadata.getMinSdk(); if (templateMinSdk > currentMinSdk && currentMinSdk >= 1) { return String.format("This template requires a minimum SDK version of at least %1$d, and the current min version is %2$d", templateMinSdk, currentMinSdk); } int templateMinBuildApi = metadata.getMinBuildApi(); if (templateMinBuildApi > buildApi && buildApi >= 1) { return String.format("This template requires a build target API version of at least %1$d, and the current version is %2$d", templateMinBuildApi, buildApi); } return null; } }