blob: 1efd60c20617c88f6d26d4ad9c64acef31eddbd3 [file] [log] [blame]
/*
* 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.npw.ConfigureAndroidModuleStep;
import com.android.tools.idea.npw.NewProjectWizardState;
import com.android.tools.idea.wizard.template.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.fileEditor.FileDocumentManager;
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.openapi.vfs.VirtualFile;
import com.intellij.testFramework.PlatformTestUtil;
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.npw.NewModuleWizardState.ATTR_CREATE_ACTIVITY;
import static com.android.tools.idea.npw.NewModuleWizardState.ATTR_PROJECT_LOCATION;
import static com.android.tools.idea.npw.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);
}
public void testTemplateFormatting() throws Exception {
Template template = Template.createFromPath(new File(getTestDataPath(), FileUtil.join("templates", "TestTemplate")));
template.render(new File(myFixture.getTempDirPath()), new File("dummy"),
Maps.<String, Object>newHashMap(), myFixture.getProject());
FileDocumentManager.getInstance().saveAllDocuments();
LocalFileSystem fileSystem = LocalFileSystem.getInstance();
VirtualFile desired = fileSystem.findFileByIoFile(new File(getTestDataPath(),
FileUtil.join("templates", "TestTemplate", "MergedStringsFile.xml")));
VirtualFile actual = fileSystem.findFileByIoFile(new File(myFixture.getTempDirPath(),
FileUtil.join("values", "TestTargetResourceFile.xml")));
desired.refresh(false, false);
actual.refresh(false, false);
PlatformTestUtil.assertFilesEqual(desired, actual);
}
// ---- 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;
}
}