blob: 143b0f87d5bf63eaeb227a4a35180914387c103e [file] [log] [blame]
/*
* Copyright (C) 2015 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.gradle.editor.parser;
import com.android.SdkConstants;
import com.android.tools.idea.gradle.editor.entity.*;
import com.google.common.collect.Lists;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase;
import com.intellij.util.Processor;
import org.jetbrains.android.AndroidTestBase;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.PropertyKey;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import static com.android.tools.idea.gradle.editor.metadata.StdGradleEditorEntityMetaData.*;
import static com.android.tools.idea.gradle.editor.parser.GradleEditorParserTestUtil.externalDependency;
import static com.android.tools.idea.gradle.editor.parser.GradleEditorParserTestUtil.property;
public class GradleEditorParserTest extends LightPlatformCodeInsightFixtureTestCase {
public static final String GRADLE_EDITOR_TEST_DATA_ROOT = "editor";
@Override
protected void setUp() throws Exception {
super.setUp();
}
public void testSingleFile_hardCodedProperties() throws Exception {
prepare("singleFileHardCodedProperties");
List<GradleEditorEntityGroup> parsed = parse("build.gradle");
VersionGradleEditorEntity pluginVersion =
findEntity("android.gradle.editor.version.gradle.plugin", VersionGradleEditorEntity.class, parsed);
property("android gradle plugin").value("1.0.0").entityText("classpath 'com.android.tools.build:gradle:1.0.0'")
.declarationValue("1.0.0").metaData(OUTGOING).check(pluginVersion);
VersionGradleEditorEntity compileSdk = findEntity("android.gradle.editor.version.sdk.compile", VersionGradleEditorEntity.class, parsed);
property("compile sdk version").value("20").entityText("compileSdkVersion 20").declarationValue("20").check(compileSdk);
VersionGradleEditorEntity buildTools = findEntity("android.gradle.editor.version.build.tools", VersionGradleEditorEntity.class, parsed);
property("build tools version").value("20.0.0").entityText("buildToolsVersion '20.0.0'").declarationValue("20.0.0").check(buildTools);
checkDependencies(parsed, externalDependency().scope("compile").group("group1").artifact("artifact1").version("version1")
.entityText("compile 'group1:artifact1:version1'").versionDeclarationValue("version1").metaData(REMOVABLE),
externalDependency().scope("test").group("group2").artifact("artifact2").version("version2")
.entityText("test 'group2:artifact2:version2'").versionDeclarationValue("version2").metaData(REMOVABLE));
}
public void testSingleFile_referencedProperties() throws IOException {
prepare("singleFileReferencedProperties");
List<GradleEditorEntityGroup> parsed = parse("build.gradle");
VersionGradleEditorEntity pluginVersion =
findEntity("android.gradle.editor.version.gradle.plugin", VersionGradleEditorEntity.class, parsed);
property("android gradle plugin").value("1.0.0").entityText("classpath \"com.android.tools.build:gradle:$GRADLE_PLUGIN_VERSION\"")
.declarationValue("$GRADLE_PLUGIN_VERSION").metaData(OUTGOING).check(pluginVersion);
VersionGradleEditorEntity compileSdk = findEntity("android.gradle.editor.version.sdk.compile", VersionGradleEditorEntity.class, parsed);
property("compile sdk version").value("").value("").definitionValueBindings("20", "21").entityText("compileSdkVersion COMPILE_SDK_VERSION")
.declarationValue("COMPILE_SDK_VERSION").check(compileSdk);
VersionGradleEditorEntity buildTools = findEntity("android.gradle.editor.version.build.tools", VersionGradleEditorEntity.class, parsed);
property("build tools version").value("").definitionValueBindings("'20.0.' + 0").entityText("buildToolsVersion BUILD_TOOLS_VERSION")
.declarationValue("BUILD_TOOLS_VERSION").check(buildTools);
checkDependencies(parsed,
externalDependency().scope("compile").group("g").artifact("a").version("").versionBinding("0 + 1")
.versionDeclarationValue("$VERSION0").entityText("compile \"g:a:$VERSION0\"").metaData(REMOVABLE),
externalDependency().scope("compile").group("group1").artifact("artifact1").version("1")
.versionDeclarationValue("$VERSION1").entityText("compile \"$GROUP1:$ARTIFACT1:$VERSION1\"").metaData(REMOVABLE),
externalDependency().scope("test").groupBinding("group2", "group22").artifactBinding("'group' + tmpLocalVar", "2")
.version("2").versionDeclarationValue("$VERSION2").entityText("test \"$GROUP2:$ARTIFACT2:$VERSION2\"")
.metaData(REMOVABLE),
externalDependency().scope("test").group("g1").artifact("a1").version("").versionBinding("1 + 2")
.versionDeclarationValue("${1 + 2}").entityText("test \"g1:a1:${1 + 2}\"").metaData(REMOVABLE));
}
public void testMultiProject_normal() throws IOException {
prepare("multiProjectNormal");
List<GradleEditorEntityGroup> subProjectParsed = parse("app/build.gradle");
VersionGradleEditorEntity subProjectPluginVersion =
findEntity("android.gradle.editor.version.gradle.plugin", VersionGradleEditorEntity.class, subProjectParsed);
property("android gradle plugin").value("1.0.0").entityText("classpath 'com.android.tools.build:gradle:1.0.0'")
.declarationValue("1.0.0").metaData(INJECTED).check(subProjectPluginVersion);
VersionGradleEditorEntity subProjectCompileSdk = findEntity("android.gradle.editor.version.sdk.compile", VersionGradleEditorEntity.class, subProjectParsed);
property("compile sdk version").value("20").entityText("compileSdkVersion COMPILE_SDK_VERSION")
.declarationValue("COMPILE_SDK_VERSION").check(subProjectCompileSdk);
checkDependencies(subProjectParsed,
externalDependency().scope("compile").group("group1").artifact("artifact1").version("version1")
.versionDeclarationValue("version1").entityText("compile 'group1:artifact1:version1'").metaData(REMOVABLE),
externalDependency().scope("unitTest").group("junit").artifact("junit").version("4.11")
.versionDeclarationValue("4.11").entityText("unitTest 'junit:junit:4.11'").metaData(INJECTED, REMOVABLE));
checkRepositories(subProjectParsed,
property("jcenter repo").value(GradleEditorRepositoryEntity.JCENTER_URL).metaData(INJECTED, READ_ONLY));
List<GradleEditorEntityGroup> baseProjectParsed = parse("build.gradle");
VersionGradleEditorEntity baseProjectPluginVersion =
findEntity("android.gradle.editor.version.gradle.plugin", VersionGradleEditorEntity.class, baseProjectParsed);
property("android gradle plugin").value("1.0.0").entityText("classpath 'com.android.tools.build:gradle:1.0.0'")
.declarationValue("1.0.0").metaData(OUTGOING).check(baseProjectPluginVersion);
checkDependencies(baseProjectParsed,
externalDependency().scope("unitTest").group("junit").artifact("junit").version("4.11")
.versionDeclarationValue("4.11").entityText("unitTest 'junit:junit:4.11'").metaData(OUTGOING, REMOVABLE));
checkRepositories(baseProjectParsed,
property("jcenter repo").value(GradleEditorRepositoryEntity.JCENTER_URL).metaData(READ_ONLY),
property("jcenter repo").value(GradleEditorRepositoryEntity.JCENTER_URL).metaData(READ_ONLY, OUTGOING),
property("maven central repo").value(GradleEditorRepositoryEntity.MAVEN_CENTRAL_URL).metaData(READ_ONLY),
property("custom maven repo").value("https://android-devtools-staging.corp.google.com/no_crawl/maven/full/")
.definitionValueBindings("https://android-devtools-staging.corp.google.com/no_crawl/maven/full/").metaData(READ_ONLY),
property("custom maven repo from env").value("").declarationValue("System.getenv(\"MAVEN_REPO\")")
.definitionValueBindings("System.getenv(\"MAVEN_REPO\")"),
property("custom maven repo from env via externalized variable name")
.declarationValue("System.getenv(\"$M2_ENV_VAR_NAME\")")
.definitionValueBindings("System.getenv(\"$M2_ENV_VAR_NAME\")", "M2_HOME"));
}
@SuppressWarnings("unchecked")
@NotNull
private static <T extends GradleEditorEntity> T findEntity(
@NotNull @PropertyKey(resourceBundle = "messages.AndroidBundle") String nameKey,
@NotNull Class<T> entityClass,
@NotNull List<GradleEditorEntityGroup> groups)
{
String name = AndroidBundle.message(nameKey);
for (GradleEditorEntityGroup group : groups) {
for (GradleEditorEntity entity : group.getEntities()) {
if (name.equals(entity.getName())) {
if (entityClass.isInstance(entity)) {
return (T)entity;
}
fail(String.format("Target entity with name '%s' is of unexpected class (%s). Expected it to be IS-A %s",
name, group.getClass().getName(), entityClass.getName()));
}
}
}
String message = String.format("Can't find an entity with name '%s' which IS-A %s", name, entityClass.getName());
fail(message);
throw new RuntimeException(message); // To make compiler happy
}
public void testRemovingEntity() throws IOException {
prepare("singleFileHardCodedProperties");
List<GradleEditorEntityGroup> parsed = parse("build.gradle");
List<ExternalDependencyGradleEditorEntity> dependencies = getDependencies(parsed);
doCheckDependencies(dependencies,
externalDependency().scope("compile").group("group1").artifact("artifact1").version("version1")
.entityText("compile 'group1:artifact1:version1'").versionDeclarationValue("version1").metaData(REMOVABLE),
externalDependency().scope("test").group("group2").artifact("artifact2").version("version2")
.entityText("test 'group2:artifact2:version2'").versionDeclarationValue("version2").metaData(REMOVABLE));
for (ExternalDependencyGradleEditorEntity dependency : dependencies) {
if ("compile".equals(dependency.getScope())) {
GradleEditorModelUtil.removeEntity(dependency, true);
break;
}
}
parsed = parse("build.gradle");
checkDependencies(parsed,
externalDependency().scope("test").group("group2").artifact("artifact2").version("version2")
.entityText("test 'group2:artifact2:version2'").versionDeclarationValue("version2").metaData(REMOVABLE));
}
private static void checkDependencies(@NotNull List<GradleEditorEntityGroup> groups, @NotNull ExternalDependencyChecker... checkers) {
doCheckDependencies(getDependencies(groups), checkers);
}
@NotNull
private static List<ExternalDependencyGradleEditorEntity> getDependencies(@NotNull List<GradleEditorEntityGroup> groups) {
return getEntities(groups, "android.gradle.editor.header.dependencies", ExternalDependencyGradleEditorEntity.class);
}
private static void doCheckDependencies(@NotNull List<ExternalDependencyGradleEditorEntity> actualEntities,
@NotNull ExternalDependencyChecker... checkers) {
assertEquals("Parsed dependencies number mismatch", checkers.length, actualEntities.size());
actualEntities = Lists.newArrayList(actualEntities);
List<ExternalDependencyChecker> checkersBuffer = Lists.newArrayList(checkers);
for (Iterator<ExternalDependencyGradleEditorEntity> entityIterator = actualEntities.iterator(); entityIterator.hasNext(); ) {
ExternalDependencyGradleEditorEntity entity = entityIterator.next();
for (Iterator<ExternalDependencyChecker> checkerIterator = checkersBuffer.iterator(); checkerIterator.hasNext(); ) {
ExternalDependencyChecker checker = checkerIterator.next();
if (checker.matches(entity) == null) {
entityIterator.remove();
checkerIterator.remove();
break;
}
}
}
if (actualEntities.isEmpty()) {
return;
}
fail(String.format("Mismatched dependencies (%d): expected but not matched: %s, unexpected dependencies: %s", actualEntities.size(),
StringUtil.join(checkersBuffer, ","), StringUtil.join(actualEntities, ",")));
}
@SuppressWarnings("unchecked")
@NotNull
private static <T> List<T> getEntities(@NotNull List<GradleEditorEntityGroup> groups,
@NotNull @PropertyKey(resourceBundle = "messages.AndroidBundle") String groupNameKey,
@NotNull Class<T> entityClass) {
List<T> result = Lists.newArrayList();
String groupName = AndroidBundle.message(groupNameKey);
for (GradleEditorEntityGroup group : groups) {
if (!groupName.equals(group.getName())) {
continue;
}
for (GradleEditorEntity entity : group.getEntities()) {
if (entityClass.isInstance(entity)) {
result.add((T)entity);
}
}
}
return result;
}
private static <T extends AbstractSimpleGradleEditorEntity> void checkEntities(@NotNull List<T> actual,
@NotNull SimpleEntityChecker<T>... checkers) {
assertEquals("Entities number mismatch", checkers.length, actual.size());
actual = Lists.newArrayList(actual);
List<SimpleEntityChecker<T>> checkersBuffer = Lists.newArrayList(checkers);
for (Iterator<T> entityIterator = actual.iterator(); entityIterator.hasNext(); ) {
T entity = entityIterator.next();
for (Iterator<SimpleEntityChecker<T>> checkerIterator = checkersBuffer.iterator(); checkerIterator.hasNext(); ) {
SimpleEntityChecker<T> checker = checkerIterator.next();
if (checker.apply(entity) == null) {
entityIterator.remove();
checkerIterator.remove();
break;
}
}
}
if (actual.isEmpty()) {
return;
}
fail(String.format("Mismatched entities (%d): expected but not matched: %s, unexpected dependencies: %s", actual.size(),
StringUtil.join(checkersBuffer, ","), StringUtil.join(actual, ",")));
}
@SuppressWarnings("unchecked")
private static void checkRepositories(@NotNull List<GradleEditorEntityGroup> groups,
@NotNull SimpleEntityChecker<?>... expected) {
List<GradleEditorRepositoryEntity> repositories =
getEntities(groups, "android.gradle.editor.header.repositories", GradleEditorRepositoryEntity.class);
checkEntities(repositories, (SimpleEntityChecker<GradleEditorRepositoryEntity>[])expected);
}
/**
* Configures ide project to use with the target test data identified by the given argument.
*
* @param relativeGradleConfigPath relative path to the project's test data root, i.e. all files under the given root
* will be copied under the project's root. Given path is relative to the
* '{@link AndroidTestBase#getTestDataPath() android test root}/{@value #GRADLE_EDITOR_TEST_DATA_ROOT}'
* @throws IOException in case of unexpected I/O exception occurred during files copying
*/
private void prepare(@NotNull String relativeGradleConfigPath) throws IOException {
File fromDir = new File(AndroidTestBase.getTestDataPath(),
FileUtil.join(GRADLE_EDITOR_TEST_DATA_ROOT, relativeGradleConfigPath.replace('/', File.separatorChar)));
assertTrue(fromDir.getAbsolutePath(), fromDir.isDirectory());
Project project = myFixture.getProject();
VirtualFile vfsProjectRoot = project.getBaseDir();
purgeGradleConfig(vfsProjectRoot);
File projectRoot = VfsUtilCore.virtualToIoFile(vfsProjectRoot);
FileUtil.copyDir(fromDir, projectRoot);
vfsProjectRoot.refresh(false, true);
}
/**
* Recursively removes all gradle config files from the given dir.
*
* @param rootDir target root dir
* @throws IOException in case of unexpected I/O exception occurred during processing
*/
private void purgeGradleConfig(@NotNull VirtualFile rootDir) throws IOException {
final List<VirtualFile> toRemove = Lists.newArrayList();
VfsUtil.processFileRecursivelyWithoutIgnored(rootDir, new Processor<VirtualFile>() {
@Override
public boolean process(VirtualFile file) {
if (file.getName().endsWith(SdkConstants.DOT_GRADLE)) {
toRemove.add(file);
}
return true;
}
});
if (toRemove.isEmpty()) {
return;
}
LocalFileSystem fileSystem = LocalFileSystem.getInstance();
for (VirtualFile file : toRemove) {
fileSystem.deleteFile(this, file);
}
}
/**
* Builds gradle editor model for the target gradle config file.
*
* @param relativeGradleConfigPath relative path to the target gradle config file for which gradle editor model should be built.
* The path is relative to the current ide project's root
* @return gradle editor model for the target gradle config file
*/
@NotNull
private List<GradleEditorEntityGroup> parse(@NotNull String relativeGradleConfigPath) {
VirtualFile baseDir = myFixture.getProject().getBaseDir();
String filePath = baseDir.getPath() + '/' + relativeGradleConfigPath;
VirtualFile vFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(filePath);
assertNotNull(filePath, vFile);
return new GradleEditorModelParserFacade().parse(vFile, myFixture.getProject());
}
}