/* * Copyright 2003-2017 JetBrains s.r.o. * * 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 jetbrains.mps.make; import com.intellij.openapi.util.Condition; import com.intellij.util.CommonProcessors.CollectProcessor; import com.intellij.util.FilteringProcessor; import jetbrains.mps.CoreMpsTest; import jetbrains.mps.extapi.module.SRepositoryExt; import jetbrains.mps.library.ModulesMiner; import jetbrains.mps.library.ModulesMiner.ModuleHandle; import jetbrains.mps.persistence.DefaultModelRoot; import jetbrains.mps.progress.EmptyProgressMonitor; import jetbrains.mps.project.AbstractModule; import jetbrains.mps.project.MPSExtentions; import jetbrains.mps.project.ModuleId; import jetbrains.mps.project.SModuleOperations; import jetbrains.mps.project.Solution; import jetbrains.mps.project.facets.JavaModuleFacet; import jetbrains.mps.project.persistence.LanguageDescriptorPersistence; import jetbrains.mps.project.persistence.SolutionDescriptorPersistence; import jetbrains.mps.project.structure.modules.Dependency; import jetbrains.mps.project.structure.modules.LanguageDescriptor; import jetbrains.mps.project.structure.modules.SolutionDescriptor; import jetbrains.mps.smodel.BaseMPSModuleOwner; import jetbrains.mps.smodel.BootstrapLanguages; import jetbrains.mps.smodel.Language; import jetbrains.mps.smodel.MPSModuleOwner; import jetbrains.mps.smodel.MPSModuleRepository; import jetbrains.mps.smodel.ModelAccessHelper; import jetbrains.mps.smodel.ModuleRepositoryFacade; import jetbrains.mps.smodel.SModelInternal; import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory; import jetbrains.mps.util.MacrosFactory; import jetbrains.mps.vfs.FileSystem; import jetbrains.mps.vfs.IFile; import jetbrains.mps.vfs.IFileUtils; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.module.ModelAccess; import org.jetbrains.mps.openapi.module.SModule; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import java.io.File; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; /** * I assume intention of this test, despite the 'Make' in the name, is to check parts of JavaCompile facet, pretending java files * are produced by previous steps (e.g. generated). There's another use-case in MPS, where a module may * reference existing Java sources (i.e. to get existing Java projects into MPS world) and MPS shall compile these as well. * However, for that case I'd expect dependencies to be expressed in a way of module dependencies, not through language and its runtime solution. * * TODO rewrite module creation via existing functionality. * FIXME shall use TestModuleFactoryBase to create modules, and createEmptyProject() instead of temp dir and solutions added there. * Once there's project, shall drop use of MPSModuleRepository (take one from Project), * @see jetbrains.mps.classloading.ModulesReloadTest */ public class TestMakeOnRealProject extends CoreMpsTest { private static final String TEST_JAVA_FILE = "Test.java"; private static ModelAccess ourModelAccess; private static SRepositoryExt ourRepository; private IFile myTmpDir; private Solution myCreatedRuntimeSolution; private Language myCreatedLanguage; private Solution myCreatedSolution; private MPSModuleOwner myModuleOwner = new BaseMPSModuleOwner(); @BeforeClass public static void setUp() { ourRepository = ENV.getPlatform().findComponent(MPSModuleRepository.class); ourModelAccess = ourRepository.getModelAccess(); } @Before public void beforeTest() throws IOException { createTmpModules(); } @After public void afterTest() throws Exception { ourModelAccess.runWriteAction(new Runnable() { public void run() { new ModuleRepositoryFacade(ourRepository).unregisterModules(myModuleOwner); } }); ourModelAccess.runWriteAction(new Runnable() { @Override public void run() { myTmpDir.delete(); myTmpDir = null; } }); } /** * Compiles all solutions in project and check that it is ok. */ private void doSolutionsCompilation() { final Set<SModule> toCompile = new LinkedHashSet<SModule>(); toCompile.add(myCreatedSolution); ourModelAccess.runReadAction(new Runnable() { public void run() { MPSCompilationResult result = new ModuleMaker().make(toCompile, new EmptyProgressMonitor()); Assert.assertTrue("Compilation is not ok!", result.isOk()); } }); } /** * Checks that solutions and language are compiled (very basic check). */ @Test public void testSolutionAndItsDependency() { doSolutionsCompilation(); ourModelAccess.runReadAction(new Runnable() { public void run() { checkModuleCompiled(myCreatedSolution); checkModuleCompiled(myCreatedRuntimeSolution); } }); } @Test public void testNothingToCompileAfterCompilation() throws InterruptedException { doSolutionsCompilation(); ModuleSources sources = new ModuleSources(myCreatedSolution, new Dependencies(Collections.<SModule>emptyList())); Assert.assertEquals(0, sources.getFilesToCompile().size()); } /** * Test for correctly scanning for changed sources. */ @Test public void testCompileAfterTouch() throws InterruptedException { doSolutionsCompilation(); IFile outputPath = myCreatedSolution.getFacet(JavaModuleFacet.class).getOutputRoot(); IFile javaFile = outputPath.getDescendant(TEST_JAVA_FILE); long time = Math.max(System.currentTimeMillis(), javaFile.lastModified() + 1); if (!FileSystem.getInstance().setTimeStamp(javaFile, time)) { Assert.fail("Can't touch the file " + javaFile); } ModuleSources sources = new ModuleSources(myCreatedSolution, new Dependencies(Collections.<SModule>emptyList())); Collection<JavaFile> filesToCompile = sources.getFilesToCompile(); Assert.assertEquals(1, filesToCompile.size()); } @Test public void testFileDelete() throws InterruptedException { doSolutionsCompilation(); IFile outputPath = myCreatedSolution.getFacet(JavaModuleFacet.class).getOutputRoot(); outputPath.getDescendant(TEST_JAVA_FILE).delete(); ModuleSources sources = new ModelAccessHelper(ourModelAccess).runReadAction(() -> new ModuleSources(myCreatedSolution, new Dependencies(Collections.singleton((SModule) myCreatedSolution)))); Collection<File> filesToDelete = sources.getFilesToDelete(); Assert.assertEquals(1, filesToDelete.size()); } private void checkModuleCompiled(SModule module) { JavaModuleFacet facet = module.getFacet(JavaModuleFacet.class); assert facet != null; IFile classesGen = facet.getClassesGen(); assert classesGen != null; List<File> classes = collectSpecificFilesFromDir(new File(classesGen.getPath()), "class"); List<File> sources = new ArrayList<File>(); for (String path : SModuleOperations.getAllSourcePaths(module)) { collectSpecificFilesFromDir(new File(path), "java", sources); } Assert.assertTrue("classes_gen should contain one class", sources.size() <= classes.size()); } private ArrayList<File> collectSpecificFilesFromDir(File file, final String extension) { ArrayList<File> classes = new ArrayList<File>(); collectSpecificFilesFromDir(file, extension, classes); return classes; } private void collectSpecificFilesFromDir(File file, final String extension, Collection<File> classes) { com.intellij.openapi.util.io.FileUtil.processFilesRecursively(file, new FilteringProcessor<File>(new Condition<File>() { public boolean value(File file) { return file.getName().endsWith("." + extension); } }, new CollectProcessor<File>(classes))); } private void createTmpModules() { ourModelAccess.runWriteAction(new Runnable() { @Override public void run() { myTmpDir = IFileUtils.createTmpDir(); myCreatedRuntimeSolution = createNewRuntimeSolution(); createJavaFiles(myCreatedRuntimeSolution); myCreatedLanguage = createNewLanguage(); createJavaFiles(myCreatedLanguage); myCreatedSolution = createNewSolution(); createJavaFiles(myCreatedSolution); IFile generatorOutputPath = myCreatedSolution.getFacet(JavaModuleFacet.class).getOutputRoot(); // XXX where from comes the assumption resources/ dir is sibling to source_gen? Why this location is not part of any facet? IFile resourceDir = generatorOutputPath.getParent().getDescendant("resources"); myCreatedSolution.getModuleDescriptor().getSourcePaths().add(resourceDir.getPath()); createFile(resourceDir, "res.0.1/test.txt", "test"); } }); } public void createJavaFiles(AbstractModule module) { createFile(module.getFacet(JavaModuleFacet.class).getOutputRoot(), TEST_JAVA_FILE, "class Test {}"); } private void createFile(IFile dir, String fileName, String text) { // should be invoked in write action FileSystem fileSystem = FileSystem.getInstance(); IFile ifile = dir.getDescendant(fileName); ifile.createNewFile(); Writer writer = null; try { writer = new OutputStreamWriter(ifile.openOutputStream()); writer.append(text); writer.flush(); } catch (IOException e) { e.printStackTrace(); } finally { if (writer != null) { try { writer.close(); } catch (IOException e) { e.printStackTrace(); } } } if (!fileSystem.setTimeStamp(ifile, System.currentTimeMillis() - 1000)) { Assert.fail("Can't touch the file " + ifile); } } private Solution createNewRuntimeSolution() { IFile runtimeSolutionDescriptorFile = myTmpDir.getDescendant("TestLanguageRuntime" + File.separator + "TestLanguageRuntime" + MPSExtentions.DOT_SOLUTION); String fileName = runtimeSolutionDescriptorFile.getName(); SolutionDescriptor solutionDescriptor = new SolutionDescriptor(); String name = fileName.substring(0, fileName.length() - 4); solutionDescriptor.setId(ModuleId.regular()); solutionDescriptor.setNamespace(name); DefaultModelRoot modelRoot = new DefaultModelRoot(); modelRoot.setContentRoot(runtimeSolutionDescriptorFile.getParent().getPath()); modelRoot.addFile(DefaultModelRoot.SOURCE_ROOTS, runtimeSolutionDescriptorFile.getParent().getPath()); solutionDescriptor.getModelRootDescriptors().add(modelRoot.toDescriptor()); solutionDescriptor.getDependencies().add(new Dependency(BootstrapLanguages.jdkRef(), true)); runtimeSolutionDescriptorFile.createNewFile(); SolutionDescriptorPersistence.saveSolutionDescriptor(runtimeSolutionDescriptorFile, solutionDescriptor, MacrosFactory.forModuleFile(runtimeSolutionDescriptorFile)); ModuleHandle handle = new ModulesMiner().loadModuleHandle(runtimeSolutionDescriptorFile); return (Solution) ModuleRepositoryFacade.createModule(handle, myModuleOwner); } private Language createNewLanguage() { String languageNamespace = "TestLanguage"; IFile descriptorFile = myTmpDir.getDescendant(languageNamespace + File.separator + languageNamespace + MPSExtentions.DOT_LANGUAGE); LanguageDescriptor d = new LanguageDescriptor(); d.setId(ModuleId.regular()); d.setNamespace(languageNamespace); d.getRuntimeModules().add(myCreatedRuntimeSolution.getModuleReference()); DefaultModelRoot modelRoot = new DefaultModelRoot(); IFile languageModels = descriptorFile.getParent().getDescendant(Language.LANGUAGE_MODELS); modelRoot.setContentRoot(languageModels.getParent().getPath()); modelRoot.addFile(DefaultModelRoot.SOURCE_ROOTS, languageModels.getPath()); d.getModelRootDescriptors().add(modelRoot.toDescriptor()); LanguageDescriptorPersistence.saveLanguageDescriptor(descriptorFile, d, MacrosFactory.forModuleFile(descriptorFile)); ModuleHandle handle = new ModulesMiner().loadModuleHandle(descriptorFile); return (Language) ModuleRepositoryFacade.createModule(handle, myModuleOwner); } private Solution createNewSolution() { IFile descriptorFile = myTmpDir.getDescendant("TestSolution" + File.separator + "testSolution" + MPSExtentions.DOT_SOLUTION); String fileName = descriptorFile.getName(); SolutionDescriptor solutionDescriptor = new SolutionDescriptor(); solutionDescriptor.setId(ModuleId.regular()); String name = fileName.substring(0, fileName.length() - 4); solutionDescriptor.setNamespace(name); DefaultModelRoot modelRoot = new DefaultModelRoot(); modelRoot.setContentRoot(descriptorFile.getParent().getPath()); modelRoot.addFile(DefaultModelRoot.SOURCE_ROOTS, descriptorFile.getParent().getPath()); solutionDescriptor.getModelRootDescriptors().add(modelRoot.toDescriptor()); SolutionDescriptorPersistence.saveSolutionDescriptor(descriptorFile, solutionDescriptor, MacrosFactory.forModuleFile(descriptorFile)); ModuleHandle handle = new ModulesMiner().loadModuleHandle(descriptorFile); final Solution rv = (Solution) ModuleRepositoryFacade.createModule(handle, myModuleOwner); final SModel m1 = rv.getModelRoots().iterator().next().createModel("m1"); ((SModelInternal) m1).addLanguage(MetaAdapterFactory.getLanguage(myCreatedLanguage.getModuleReference())); return rv; } }