blob: 7750a32e67c14480e51b03ca77a623e2baa5d28c [file] [log] [blame]
/*
* Copyright 2017 The Bazel Authors. All rights reserved.
*
* 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.google.idea.blaze.cpp;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
import com.google.idea.blaze.base.ideinfo.CIdeInfo;
import com.google.idea.blaze.base.ideinfo.CToolchainIdeInfo;
import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
import com.google.idea.blaze.base.ideinfo.TargetMap;
import com.google.idea.blaze.base.ideinfo.TargetMapBuilder;
import com.google.idea.blaze.base.model.BlazeProjectData;
import com.google.idea.blaze.base.model.MockBlazeProjectDataBuilder;
import com.google.idea.blaze.base.model.MockBlazeProjectDataManager;
import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
import com.google.idea.blaze.base.model.primitives.Kind;
import com.google.idea.blaze.base.model.primitives.TargetExpression;
import com.google.idea.blaze.base.model.primitives.WorkspacePath;
import com.google.idea.blaze.base.projectview.ProjectView;
import com.google.idea.blaze.base.projectview.ProjectViewSet;
import com.google.idea.blaze.base.projectview.section.ListSection;
import com.google.idea.blaze.base.projectview.section.sections.DirectoryEntry;
import com.google.idea.blaze.base.projectview.section.sections.DirectorySection;
import com.google.idea.blaze.base.projectview.section.sections.TargetSection;
import com.google.idea.blaze.base.scope.BlazeContext;
import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
import com.jetbrains.cidr.lang.psi.OCFile;
import com.jetbrains.cidr.lang.psi.OCReferenceElement;
import com.jetbrains.cidr.lang.quickfixes.OCImportSymbolFix;
import com.jetbrains.cidr.lang.symbols.symtable.FileSymbolTablesCache;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests that {@link BlazeCppAutoImportHelper} is able to get the correct form of #include for the
* {@link OCImportSymbolFix} quickfix, given typical workspace layouts / location of system headers.
*/
@RunWith(JUnit4.class)
public class BlazeCppAutoImportHelperTest extends BlazeCppIntegrationTestCase {
@Before
public void setup() {
createHeaderRoots();
registerApplicationService(
CompilerVersionChecker.class, new MockCompilerVersionChecker("1234"));
}
@Test
public void stlPathsUnderWorkspaceRoot_importStlHeader() {
ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:bar"));
TargetMap targetMap =
TargetMapBuilder.builder()
.addTarget(createCcToolchain())
.addTarget(
createCcTarget(
"//foo/bar:bar", Kind.CC_LIBRARY, sources("foo/bar/bar.cc"), sources()))
.addTarget(
createCcTarget(
"//third_party/stl:stl",
Kind.CC_LIBRARY,
sources(),
sources("third_party/stl/vector.h")))
.build();
// Normally this is <vector> without .h, but we need to trick the file type detector into
// realizing that this is an OCFile.
OCFile header =
createFile(
"third_party/stl/vector.h",
"namespace std {",
"template<typename T> class vector {};",
"}");
OCFile file = createFile("foo/bar/bar.cc", "std::vector<int> my_vector;");
resolve(projectView, targetMap, file, header);
testFixture.openFileInEditor(file.getVirtualFile());
OCReferenceElement referenceElement =
testFixture.findElementByText("std::vector<int>", OCReferenceElement.class);
OCImportSymbolFix fix = new OCImportSymbolFix(referenceElement);
assertThat(fix.isAvailable(getProject(), testFixture.getEditor(), file)).isTrue();
assertThat(fix.getAutoImportItems()).hasSize(1);
assertThat(fix.getAutoImportItems().get(0).getTitleAndLocation().getFirst())
.isEqualTo("class 'std::vector'");
assertThat(fix.getAutoImportItems().get(0).getTitleAndLocation().getSecond())
.isEqualTo("<vector.h>");
}
@Test
public void sameDirectory_importUserHeader() {
ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:bar"));
TargetMap targetMap =
TargetMapBuilder.builder()
.addTarget(createCcToolchain())
.addTarget(
createCcTarget(
"//foo/bar:bar",
Kind.CC_LIBRARY,
sources("foo/bar/bar.cc"),
sources("foo/bar/test.h")))
.build();
OCFile header = createFile("foo/bar/test.h", "class SomeClass {};");
OCFile file = createFile("foo/bar/bar.cc", "SomeClass* my_class = new SomeClass();");
resolve(projectView, targetMap, file, header);
testFixture.openFileInEditor(file.getVirtualFile());
OCReferenceElement referenceElement =
testFixture.findElementByText("SomeClass*", OCReferenceElement.class);
OCImportSymbolFix fix = new OCImportSymbolFix(referenceElement);
assertThat(fix.isAvailable(getProject(), testFixture.getEditor(), file)).isTrue();
assertThat(fix.getAutoImportItems()).hasSize(1);
assertThat(fix.getAutoImportItems().get(0).getTitleAndLocation().getFirst())
.isEqualTo("class 'SomeClass'");
assertThat(fix.getAutoImportItems().get(0).getTitleAndLocation().getSecond())
.isEqualTo("\"foo/bar/test.h\"");
}
@Test
public void differentDirectory_importUserHeader() {
ProjectView projectView =
projectView(directories("foo/bar", "baz"), targets("//foo/bar", "//baz"));
TargetMap targetMap =
TargetMapBuilder.builder()
.addTarget(createCcToolchain())
.addTarget(
createCcTarget(
"//foo/bar:bar", Kind.CC_LIBRARY, sources("foo/bar/bar.cc"), sources()))
.addTarget(
createCcTarget("//baz:baz", Kind.CC_LIBRARY, sources(""), sources("baz/test.h")))
.build();
OCFile header = createFile("baz/test.h", "class SomeClass {};");
OCFile file = createFile("foo/bar/bar.cc", "SomeClass* my_class = new SomeClass();");
resolve(projectView, targetMap, file, header);
testFixture.openFileInEditor(file.getVirtualFile());
OCReferenceElement referenceElement =
testFixture.findElementByText("SomeClass*", OCReferenceElement.class);
OCImportSymbolFix fix = new OCImportSymbolFix(referenceElement);
assertThat(fix.isAvailable(getProject(), testFixture.getEditor(), file)).isTrue();
assertThat(fix.getAutoImportItems()).hasSize(1);
assertThat(fix.getAutoImportItems().get(0).getTitleAndLocation().getFirst())
.isEqualTo("class 'SomeClass'");
assertThat(fix.getAutoImportItems().get(0).getTitleAndLocation().getSecond())
.isEqualTo("\"baz/test.h\"");
}
private static List<ArtifactLocation> sources(String... paths) {
return Arrays.stream(paths)
.map(path -> ArtifactLocation.builder().setRelativePath(path).setIsSource(true).build())
.collect(Collectors.toList());
}
private void createHeaderRoots() {
workspace.createDirectory(new WorkspacePath("output/genfiles"));
workspace.createDirectory(new WorkspacePath("include/third_party/libxml/_/libxml"));
workspace.createDirectory(new WorkspacePath("third_party/stl"));
workspace.createDirectory(new WorkspacePath("third_party/lib_that_expects_angle_include"));
workspace.createDirectory(new WorkspacePath("third_party/toolchain/include/c++/4.9"));
}
private TargetIdeInfo.Builder createCcTarget(
String label, Kind kind, List<ArtifactLocation> sources, List<ArtifactLocation> headers) {
TargetIdeInfo.Builder targetInfo =
TargetIdeInfo.builder().setLabel(label).setKind(kind).addDependency("//:toolchain");
sources.forEach(targetInfo::addSource);
return targetInfo.setCInfo(
CIdeInfo.builder()
.addSources(sources)
.addHeaders(headers)
.addTransitiveIncludeDirectories(
ImmutableList.of(new ExecutionRootPath("include/third_party/libxml/_/libxml")))
.addTransitiveQuoteIncludeDirectories(
ImmutableList.of(
new ExecutionRootPath("."), new ExecutionRootPath("output/genfiles")))
.addTransitiveSystemIncludeDirectories(
ImmutableList.of(
new ExecutionRootPath("third_party/stl"),
new ExecutionRootPath("third_party/lib_that_expects_angle_include"))));
}
private static TargetIdeInfo.Builder createCcToolchain() {
return TargetIdeInfo.builder()
.setLabel("//:toolchain")
.setKind(Kind.CC_TOOLCHAIN)
.setCToolchainInfo(
CToolchainIdeInfo.builder()
.setCppExecutable(new ExecutionRootPath("cc"))
.addBuiltInIncludeDirectories(
ImmutableList.of(
new ExecutionRootPath("third_party/toolchain/include/c++/4.9"))));
}
private static ListSection<DirectoryEntry> directories(String... directories) {
return ListSection.builder(DirectorySection.KEY)
.addAll(
Arrays.stream(directories)
.map(directory -> DirectoryEntry.include(WorkspacePath.createIfValid(directory)))
.collect(Collectors.toList()))
.build();
}
private static ListSection<TargetExpression> targets(String... targets) {
return ListSection.builder(TargetSection.KEY)
.addAll(
Arrays.stream(targets).map(TargetExpression::fromString).collect(Collectors.toList()))
.build();
}
private static ProjectView projectView(
ListSection<DirectoryEntry> directories, ListSection<TargetExpression> targets) {
return ProjectView.builder().add(directories).add(targets).build();
}
private MockBlazeProjectDataBuilder projectDataBuilder() {
return MockBlazeProjectDataBuilder.builder(workspaceRoot)
.setOutputBase(fileSystem.getRootDir() + "/output");
}
private void resolve(ProjectView projectView, TargetMap targetMap, OCFile... files) {
BlazeProjectData blazeProjectData = projectDataBuilder().setTargetMap(targetMap).build();
registerProjectService(
BlazeProjectDataManager.class, new MockBlazeProjectDataManager(blazeProjectData));
BlazeCWorkspace.getInstance(getProject())
.update(
new BlazeContext(),
workspaceRoot,
ProjectViewSet.builder().add(projectView).build(),
blazeProjectData);
for (OCFile file : files) {
resetFileSymbols(file);
}
FileSymbolTablesCache.getInstance(getProject()).ensurePendingFilesProcessed();
}
private void resetFileSymbols(OCFile file) {
FileSymbolTablesCache.getInstance(getProject()).handleFileChange(file, true);
}
}