/* * Copyright 2012-2015 Sergey Ignatov * * 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 org.intellij.erlang.build; import com.intellij.compiler.impl.ModuleCompileScope; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.compiler.CompileScope; import com.intellij.openapi.compiler.CompilerMessage; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.module.ModuleType; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.projectRoots.SdkModificator; import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil; import com.intellij.openapi.roots.CompilerModuleExtension; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.roots.ModuleRootModificationUtil; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.util.ThrowableComputable; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.testFramework.CompilerTester; import com.intellij.testFramework.PlatformTestCase; import com.intellij.testFramework.PsiTestUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import org.intellij.erlang.facet.ErlangFacet; import org.intellij.erlang.jps.model.ErlangIncludeSourceRootType; import org.intellij.erlang.jps.model.JpsErlangSdkType; import org.intellij.erlang.module.ErlangModuleType; import org.intellij.erlang.sdk.ErlangSdkType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.model.java.JavaSourceRootType; import org.jetbrains.jps.model.module.JpsModuleSourceRootType; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.List; public abstract class ErlangCompilationTestBase extends PlatformTestCase { protected CompilationRunner myCompilationRunner; @Override protected void setUp() throws Exception { super.setUp(); myCompilationRunner = new CompilationRunner(myModule); ApplicationManager.getApplication().runWriteAction((ThrowableComputable<Object, Exception>) () -> { createSdk(); setupSourceRootsFacetAndSdk(myModule); return null; }); } private void createSdk() { Sdk sdk = SdkConfigurationUtil.createAndAddSDK(JpsErlangSdkType.getTestsSdkPath(), ErlangSdkType.getInstance()); if (sdk == null) { fail(JpsErlangSdkType.getSdkConfigurationFailureMessage()); } // Erlang SDK can contain symlinks to files outside of allowed root set. // So we remove all roots from the sdk as we don't need SDK contents anyway. SdkModificator sdkModificator = sdk.getSdkModificator(); sdkModificator.removeAllRoots(); sdkModificator.commitChanges(); ProjectRootManager.getInstance(myProject).setProjectSdk(sdk); } @Override protected ModuleType getModuleType() { return ErlangModuleType.getInstance(); } @Override protected void tearDown() throws Exception { try { myCompilationRunner.tearDown(); } finally { super.tearDown(); } } @Override protected boolean isRunInWriteAction() { return false; } @NotNull protected Module createModuleInOwnDirectoryWithSourceAndTestRoot(final String moduleName) throws Exception { return ApplicationManager.getApplication().runWriteAction((ThrowableComputable<Module, IOException>) () -> { Module module = createModuleInDirectory(moduleName); setupSourceRootsFacetAndSdk(module); return module; }); } private static void setupSourceRootsFacetAndSdk(@NotNull Module module) throws IOException { ModuleRootModificationUtil.setSdkInherited(module); ErlangFacet.createFacet(module); addSourceRoot(module, "src", false); addSourceRoot(module, "tests", true); } @NotNull private Module createModuleInDirectory(String moduleName) throws IOException { VirtualFile baseDir = VfsUtil.createDirectoryIfMissing(myProject.getBaseDir(), moduleName); File moduleFile = new File(FileUtil.toSystemDependentName(baseDir.getPath()), moduleName + ".iml"); FileUtil.createIfDoesntExist(moduleFile); myFilesToDelete.add(moduleFile); VirtualFile virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(moduleFile); assertNotNull(virtualFile); return ModuleManager.getInstance(myProject).newModule(virtualFile.getPath(), ErlangModuleType.getInstance().getId()); } private CompileScope createModulesCompileScope(Module[] modules) { return new ModuleCompileScope(myProject, modules, false); } protected void compileAndAssertOutput(boolean withTest) throws Exception { myCompilationRunner.compile(); assertSourcesCompiled(myModule, false); if (withTest) { assertSourcesCompiled(myModule, true); } } protected static VirtualFile addTestFile(Module module, String relativePath, String content) throws IOException { return addFile(module, relativePath, content, true); } protected static VirtualFile addSourceFile(Module module, String relativePath, String content) throws IOException { return addFile(module, relativePath, content, false); } private static VirtualFile addFile(final @NotNull Module module, final @NotNull String relativePath, final @NotNull String content, final boolean toTests) throws IOException { return ApplicationManager.getApplication().runWriteAction((ThrowableComputable<VirtualFile, IOException>) () -> { JavaSourceRootType rootType = toTests ? JavaSourceRootType.TEST_SOURCE : JavaSourceRootType.SOURCE; List<VirtualFile> sourceRoots = ModuleRootManager.getInstance(module).getSourceRoots(rootType); VirtualFile sourceDir = ContainerUtil.getFirstItem(sourceRoots); assertNotNull(sourceDir); return addFile(sourceDir, relativePath, content); }); } protected static VirtualFile addFileToDirectory(final @NotNull VirtualFile sourceDir, final @NotNull String relativePath, final @NotNull String content) throws IOException { return ApplicationManager.getApplication().runWriteAction((ThrowableComputable<VirtualFile, IOException>) () -> addFile(sourceDir, relativePath, content)); } protected static VirtualFile addIncludeRoot(@NotNull final Module module, @NotNull final String sourceRootName) throws IOException { return ApplicationManager.getApplication().runWriteAction((ThrowableComputable<VirtualFile, IOException>) () -> addSourceRoot(module, sourceRootName, ErlangIncludeSourceRootType.INSTANCE)); } protected static void addGlobalParseTransform(final Module module, final Collection<String> parseTransform) { ApplicationManager.getApplication().runWriteAction(() -> { ErlangFacet erlangFacet = ErlangFacet.getFacet(module); assertNotNull(erlangFacet); erlangFacet.getConfiguration().addParseTransforms(parseTransform); }); } protected static void assertSourcesCompiled(@NotNull Module module, boolean tests) { String[] sources = getSourceFiles(module, tests); assertContains(getOutputDirectory(module, tests), ContainerUtil.mapNotNull(sources, ErlangCompilationTestBase::getExpectedOutputFileName)); } private static void assertContains(@Nullable VirtualFile parentPath, List<String> fileNames) { assertNotNull(parentPath); List<String> actual = getChildrenNames(parentPath); assertUnorderedElementsAreEqual(actual, fileNames); } private static <T> void assertUnorderedElementsAreEqual(Collection<T> actual, Collection<T> expected) { assertEquals(ContainerUtil.newHashSet(expected), ContainerUtil.newHashSet(actual)); } @Nullable protected static File getOutputFile(Module module, VirtualFile sourceFile, boolean isTest) { VirtualFile outputDirectory = getOutputDirectory(module, isTest); assertNotNull(outputDirectory); String expectedOutputFileName = getExpectedOutputFileName(sourceFile.getName()); return expectedOutputFileName == null ? null : new File(outputDirectory.getCanonicalPath(), expectedOutputFileName); } protected static long lastOutputModificationTime(@NotNull Module module, @NotNull VirtualFile sourceFile) { File outputFile = getOutputFile(module, sourceFile, false); assertNotNull(outputFile); return outputFile.lastModified(); } @NotNull private static VirtualFile addFile(VirtualFile sourceDir, String relativePath, String content) throws IOException { VirtualFile sourceFile = sourceDir.createChildData(ErlangCompilationTestBase.class, relativePath); VfsUtil.saveText(sourceFile, content); return sourceFile; } @NotNull private static VirtualFile addSourceRoot(@NotNull Module module, @NotNull String sourceRootName, @NotNull JpsModuleSourceRootType<?> rootType) throws IOException { VirtualFile moduleFile = module.getModuleFile(); assertNotNull(moduleFile); PsiTestUtil.addContentRoot(module, moduleFile.getParent()); VirtualFile[] contentRoots = ModuleRootManager.getInstance(module).getContentRoots(); assertSize(1, contentRoots); VirtualFile sourceDir = contentRoots[0].createChildDirectory(ErlangCompilationTestBase.class, sourceRootName); PsiTestUtil.addSourceRoot(module, sourceDir, rootType); return sourceDir; } @NotNull private static VirtualFile addSourceRoot(@NotNull Module module, @NotNull String sourceRootName, boolean isTestSourceRoot) throws IOException { JavaSourceRootType rootType = isTestSourceRoot? JavaSourceRootType.TEST_SOURCE:JavaSourceRootType.SOURCE; return addSourceRoot(module, sourceRootName,rootType); } @NotNull private static String[] getSourceFiles(@NotNull Module module, boolean isTestSources) { List<VirtualFile> sourceRoots = ModuleRootManager.getInstance(module).getSourceRoots(JavaSourceRootType.SOURCE); if (isTestSources) { List<VirtualFile> testRoots = ModuleRootManager.getInstance(module).getSourceRoots(JavaSourceRootType.TEST_SOURCE); sourceRoots = ContainerUtil.concat(sourceRoots, testRoots); } List<String> result = ContainerUtil.concat(ContainerUtil.mapNotNull(sourceRoots, (Function<VirtualFile, List<String>>) ErlangCompilationTestBase::getChildrenNames)); return ArrayUtil.toStringArray(result); } @Nullable private static VirtualFile getOutputDirectory(@NotNull Module module, boolean isTest) { CompilerModuleExtension instance = CompilerModuleExtension.getInstance(module); assertNotNull(instance); return isTest ? instance.getCompilerOutputPathForTests() : instance.getCompilerOutputPath(); } @Nullable private static String getExpectedOutputFileName(@NotNull String relativePath) { File file = new File(relativePath); String name = FileUtil.getNameWithoutExtension(file); CharSequence extension = FileUtilRt.getExtension(relativePath); if ("erl".equals(extension)) { return name + ".beam"; } if ("app".equals(extension) || "app.src".equals(extension)) { return name + ".app"; } return null; } @NotNull private static List<String> getChildrenNames(VirtualFile root) { return ContainerUtil.mapNotNull(root.getChildren(), VirtualFile::getName); } protected class CompilationRunner { private CompileScope myScope; private CompilerTester myTester; CompilationRunner(@NotNull Module... moduleNames) throws Exception { this(createModulesCompileScope(moduleNames)); } CompilationRunner(@NotNull CompileScope scope) throws Exception { myScope = scope; myTester = new CompilerTester(myProject, Arrays.asList(scope.getAffectedModules())); } public void compile() { List<CompilerMessage> messages = myTester.make(myScope); for (CompilerMessage message : messages) { switch (message.getCategory()) { case ERROR: fail(message.getMessage()); break; case WARNING: LOG.warn(message.getMessage()); break; case INFORMATION: LOG.info(message.getMessage()); break; case STATISTICS: LOG.info(message.getMessage()); break; default: throw new AssertionError(); } } } public void touch(@NotNull VirtualFile file) throws IOException { myTester.touch(file); } public void tearDown() { myTester.tearDown(); } } }