/* * 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.generator.impl.plan; import jetbrains.mps.PlatformMpsTest; import jetbrains.mps.generator.GenerationFacade; import jetbrains.mps.generator.GenerationOptions; import jetbrains.mps.generator.GenerationOptions.OptionsBuilder; import jetbrains.mps.generator.GenerationStatus; import jetbrains.mps.generator.ModelGenerationPlan; import jetbrains.mps.generator.ModelGenerationPlan.Checkpoint; import jetbrains.mps.generator.ModelGenerationPlan.Transform; import jetbrains.mps.generator.RigidGenerationPlan; import jetbrains.mps.generator.TransientModelsProvider; import jetbrains.mps.generator.impl.GenPlanTranslator; import jetbrains.mps.generator.impl.ModelStreamProviderImpl; import jetbrains.mps.generator.plan.CheckpointIdentity; import jetbrains.mps.generator.plan.PlanIdentity; import jetbrains.mps.generator.runtime.TemplateMappingConfiguration; import jetbrains.mps.generator.runtime.TemplateModel; import jetbrains.mps.generator.runtime.TemplateModule; import jetbrains.mps.messages.IMessageHandler; import jetbrains.mps.messages.LogHandler; import jetbrains.mps.progress.EmptyProgressMonitor; import jetbrains.mps.project.Project; import jetbrains.mps.smodel.Generator; import jetbrains.mps.smodel.ModelAccessHelper; import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory; import jetbrains.mps.smodel.language.GeneratorRuntime; import jetbrains.mps.smodel.language.LanguageRegistry; import jetbrains.mps.util.Computable; import jetbrains.mps.util.PathManager; import org.apache.log4j.Logger; import org.hamcrest.CoreMatchers; import org.jetbrains.mps.openapi.language.SLanguage; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.model.SModelName; import org.jetbrains.mps.openapi.model.SModelReference; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.model.SNodeId; import org.jetbrains.mps.openapi.module.ModelAccess; import org.jetbrains.mps.openapi.module.SModule; import org.jetbrains.mps.openapi.module.SModuleReference; import org.jetbrains.mps.openapi.persistence.PersistenceFacade; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ErrorCollector; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * @author Artem Tikhomirov */ public class CheckpointModelTest extends PlatformMpsTest { private static Project mpsProject; @Rule public final ErrorCollector myErrors = new ErrorCollector(); private final IMessageHandler myGeneratorMessages = new LogHandler(Logger.getLogger(CheckpointModelTest.class)); @BeforeClass public static void setup() { mpsProject = ENV.openProject(new File(PathManager.getUserDir(), "languages/languageDesign/generator/project.xmodel.test1")); } @AfterClass public static void tearDown() { mpsProject.dispose(); } /** * beanmodel1.mps is transformed Bean --> Class with a single checkpoint. * This test ensures there's checkpoint model and appropriate mapping label recorded. */ @Test public void createModelWithOneCheckpoint() { final SModelReference mr = PersistenceFacade.getInstance().createModelReference( "r:24638668-c917-4da1-8069-8ddef862314d(jetbrains.mps.generator.crossmodel.sandbox.beanmodel1)"); // "r:53fbbbd7-a01f-458c-a76d-a34ed2d6f25f(jetbrains.mps.generator.crossmodel.sandbox.beanmodel2)" final SModel m = resolve(mr); final PlanIdentity planIdentity = new PlanIdentity("test Plan"); final Checkpoint cp1 = new Checkpoint(new CheckpointIdentity(planIdentity, "aaa")); ModelGenerationPlan plan = new ModelAccessHelper(mpsProject.getModelAccess()).runReadAction(new Computable<ModelGenerationPlan>() { @Override public ModelGenerationPlan compute() { final Transform step1 = new Transform(getCrossmodelPropertyGenerators()); final Transform step2 = new Transform(getBaseLanguageGenerators()); return new RigidGenerationPlan(planIdentity, step1, cp1, step2); } }); GenerationOptions opt = GenerationOptions.getDefaults().customPlan(m, plan).create(); final TransientModelsProvider tmProvider = mpsProject.getComponent(TransientModelsProvider.class); // need write for process(x, model) to construct transient module, and need model read to run transformation GenerationStatus genStatus = new ModelAccessHelper(mpsProject.getRepository()).runWriteAction(() -> { GenerationFacade genFacade = new GenerationFacade(mpsProject.getRepository(), opt).transients(tmProvider).messages(myGeneratorMessages); tmProvider.initCheckpointModule(); GenerationStatus rv = genFacade.process(new EmptyProgressMonitor(), m); tmProvider.publishAll(); return rv; }); myErrors.checkThat("Generation succeeds", genStatus.isOk(), CoreMatchers.equalTo(true)); // Now I can access CME from GenerationStatus, but keep new CME to verify the environment is capable // to get populated from transient models. In fact, GenerationStatus shall give access to smth directed // more towards serialization of changed CP models, rather than generic CME. CrossModelEnvironment cme = new CrossModelEnvironment(tmProvider, new ModelStreamProviderImpl()); // XXX shall it be CME to give access to module with checkpoint models? Is there better way to find out cpModel? SModel cpModel = new ModelAccessHelper(mpsProject.getModelAccess()).runReadAction(() -> { SModule checkpointModule = tmProvider.getCheckpointsModule(); final SModelName cpModelName = CrossModelEnvironment.createCheckpointModelName(m.getReference(), cp1.getIdentity()); for (SModel trm : checkpointModule.getModels()) { if (cpModelName.equals(trm.getName())) { return trm; } } return null; }); myErrors.checkThat("Checkpoint model", cpModel, CoreMatchers.notNullValue()); ModelCheckpoints modelCheckpoints = cme.getState(m); myErrors.checkThat("CrossModelEnvironment: state present", modelCheckpoints, CoreMatchers.notNullValue()); CheckpointState cpState = modelCheckpoints.find(cp1); myErrors.checkThat("CheckpointState present", cpState, CoreMatchers.notNullValue()); if (cpState != null) { Collection<String> mappingLabels = cpState.getMappingLabels(); myErrors.checkThat("GetterMethod label present", mappingLabels.contains("GetterMethod"), CoreMatchers.equalTo(true)); } } /** * entity1.mps is transformed with two generators, Entity --> Bean --> Class. There are two checkpoints, for Beans and for Classes models. * Here we ensure there are mapping labels in both checkpoints, and that output discovered with the first label matches input of a label from second CP. */ @Test public void createModelWithTwoCheckpoints() { final SModelReference mr = PersistenceFacade.getInstance().createModelReference( "r:05c2f926-57b0-4b6d-930c-1aabb187694d(jetbrains.mps.generator.crossmodel.sandbox.entrymodel1)"); final SModel m = resolve(mr); final PlanIdentity planIdentity = new PlanIdentity(" test plan #2"); final Checkpoint cp1 = new Checkpoint(new CheckpointIdentity(planIdentity, "aaa")); final Checkpoint cp2 = new Checkpoint(new CheckpointIdentity(planIdentity, "bbb")); ModelGenerationPlan plan = new ModelAccessHelper(mpsProject.getModelAccess()).runReadAction(new Computable<ModelGenerationPlan>() { @Override public ModelGenerationPlan compute() { final Transform step1 = new Transform(getCrossmodelEntityGenerators()); final Transform step2 = new Transform(getCrossmodelPropertyGenerators()); final Transform step3 = new Transform(getBaseLanguageGenerators()); return new RigidGenerationPlan(planIdentity, step1, cp1, step2, cp2, step3); } }); GenerationOptions opt = GenerationOptions.getDefaults().customPlan(m, plan).create(); final TransientModelsProvider tmProvider = mpsProject.getComponent(TransientModelsProvider.class); // write lock - see above #createModelWithOneCheckpoint GenerationStatus genStatus = new ModelAccessHelper(mpsProject.getRepository()).runWriteAction(() -> { GenerationFacade genFacade = new GenerationFacade(mpsProject.getRepository(), opt).transients(tmProvider).messages(myGeneratorMessages); tmProvider.initCheckpointModule(); GenerationStatus rv = genFacade.process(new EmptyProgressMonitor(), m); tmProvider.publishAll(); return rv; }); myErrors.checkThat("Generation succeeds", genStatus.isOk(), CoreMatchers.equalTo(true)); CrossModelEnvironment cme = new CrossModelEnvironment(tmProvider, new ModelStreamProviderImpl()); ModelCheckpoints modelCheckpoints = cme.getState(m); boolean crossModelCheckpointsPresent = modelCheckpoints != null; myErrors.checkThat("CrossModelEnvironment: state present", crossModelCheckpointsPresent, CoreMatchers.equalTo(true)); if (!crossModelCheckpointsPresent) { return; } final CheckpointState cp1State = modelCheckpoints.find(cp1); final CheckpointState cp2State = modelCheckpoints.find(cp2); myErrors.checkThat("state for the first checkpoint present", cp1State, CoreMatchers.notNullValue()); myErrors.checkThat("state for the second checkpoint present", cp2State, CoreMatchers.notNullValue()); if (cp1State == null || cp2State == null) { return; } final String ml1 = "EntryOne2Property"; final boolean ml1Present = cp1State.getMappingLabels().contains(ml1); final String ml2 = "GetterMethod"; final boolean ml2Present = cp2State.getMappingLabels().contains(ml2); myErrors.checkThat("Entry -> BeanProperty label present", ml1Present, CoreMatchers.equalTo(true)); myErrors.checkThat("BeanProperty -> InstanceMethodDeclaration label present", ml2Present, CoreMatchers.equalTo(true)); if (ml1Present && ml2Present) { mpsProject.getModelAccess().runReadAction(new Runnable() { @Override public void run() { Collection<SNodeId> entryOneInputs = cp1State.getInputs(ml1); myErrors.checkThat("There were two Entry(kind:ONE)", entryOneInputs.size(), CoreMatchers.equalTo(2)); for (SNodeId in : entryOneInputs) { SNode originalInput = m.getNode(in); myErrors.checkThat("Original model doesn't contain Entry we've got label recorded for", originalInput, CoreMatchers.notNullValue()); if (originalInput == null) { continue; } Collection<SNode> outputAtCheckpoint1 = cp1State.getOutput(ml1, originalInput); myErrors.checkThat("Output at first checkpoint", outputAtCheckpoint1.isEmpty(), CoreMatchers.equalTo(false)); for (SNode cp1Out : outputAtCheckpoint1) { Collection<SNode> outputAtCheckpoint2 = cp2State.getOutput(ml2, cp1Out); myErrors.checkThat("Output at second checkpoint", outputAtCheckpoint2.isEmpty(), CoreMatchers.equalTo(false)); } } } }); } } @Test public void testTwoModelsIndividually() { final SModelReference mr1 = PersistenceFacade.getInstance().createModelReference("r:a2bc1c51-b81b-4f90-a208-04e6bd08c9c2(jetbrains.mps.generator.xmodel.test.m1)"); final SModelReference mr2 = PersistenceFacade.getInstance().createModelReference("r:1ae0d5a3-32c6-406d-9a53-f40b122309f5(jetbrains.mps.generator.xmodel.test.m2)"); GenerationStatus[] genStatus = new GenerationStatus[2]; mpsProject.getModelAccess().runWriteAction(() -> { SLanguage lang1 = MetaAdapterFactory.getLanguage(0xb2d9d19b9a4747a4L, 0x93f40c9390001bf2L, "jetbrains.mps.generator.test.xmodel.lang1"); final Transform step1 = new Transform(getGenerators(lang1)); final Transform step2 = new Transform(getBaseLanguageGenerators()); final PlanIdentity planIdentity = new PlanIdentity("test.plan.3"); final Checkpoint cp1 = new Checkpoint(new CheckpointIdentity(planIdentity, "aaa")); ModelGenerationPlan plan = new RigidGenerationPlan(planIdentity, step1, cp1, step2); SModel m1 = resolve(mr1); OptionsBuilder optBuilder = GenerationOptions.getDefaults(); GenerationOptions opt = optBuilder.customPlan(m1, plan).create(); final TransientModelsProvider tmProvider = mpsProject.getComponent(TransientModelsProvider.class); GenerationFacade genFacade = new GenerationFacade(mpsProject.getRepository(), opt).transients(tmProvider).messages(myGeneratorMessages); tmProvider.initCheckpointModule(); genStatus[0] = genFacade.process(new EmptyProgressMonitor(), m1); tmProvider.publishAll(); SModel m2 = resolve(mr2); // although could, don't want to put plan for m2 right along with plan for m1, want to have them separate opt = optBuilder.customPlan(m2, plan).create(); genFacade = new GenerationFacade(mpsProject.getRepository(), opt).transients(tmProvider).messages(myGeneratorMessages); tmProvider.initCheckpointModule(); genStatus[1] = genFacade.process(new EmptyProgressMonitor(), m2); tmProvider.publishAll(); }); myErrors.checkThat("m1 generation succeeds", genStatus[0].isOk(), CoreMatchers.equalTo(true)); myErrors.checkThat("m2 generation succeeds", genStatus[1].isOk(), CoreMatchers.equalTo(true)); } @Test public void testPlanTransformer() { final SModelReference planModelRef = PersistenceFacade.getInstance().createModelReference("r:85a0bc80-fc68-485e-a9a1-926c3cc284af(jetbrains.mps.generator.xmodel.test.plan1@genplan)"); ModelGenerationPlan plan = new ModelAccessHelper(mpsProject.getModelAccess()).runReadAction(new Computable<ModelGenerationPlan>() { @Override public ModelGenerationPlan compute() { SModel planModel = resolve(planModelRef); RigidPlanBuilder planBuilder = new RigidPlanBuilder(LanguageRegistry.getInstance(mpsProject.getRepository())); GenPlanTranslator planTranslator = new GenPlanTranslator(planModel.getRootNodes().iterator().next()); planTranslator.feed(planBuilder); return planBuilder.wrapUp(planTranslator.getPlanIdentity()); } }); Assert.assertNotNull(plan); Assert.assertEquals(3, plan.getSteps().size()); myErrors.checkThat(plan.getSteps().get(0), CoreMatchers.instanceOf(Transform.class)); myErrors.checkThat(plan.getSteps().get(1), CoreMatchers.instanceOf(Checkpoint.class)); myErrors.checkThat(plan.getSteps().get(2), CoreMatchers.instanceOf(Transform.class)); Transform s1 = (Transform) plan.getSteps().get(0); Checkpoint s2 = (Checkpoint) plan.getSteps().get(1); Transform s3 = (Transform) plan.getSteps().get(2); myErrors.checkThat(s1.getTransformations().isEmpty(), CoreMatchers.equalTo(false)); myErrors.checkThat(s3.getTransformations().isEmpty(), CoreMatchers.equalTo(false)); myErrors.checkThat(s2.getName(), CoreMatchers.equalTo("first")); // XXX mgp.getIdentity, provided there's CP.getIdentity()? myErrors.checkThat(s2.getIdentity().getPlan().getPersistenceValue(), CoreMatchers.equalTo("plan_a")); } @Test public void testPlanBuilder() { // final SModuleReference smodelGenerator = PersistenceFacade.getInstance().createModuleReference("2bdcefec-ba49-4b32-ab50-ebc7a41d5090()"); final SModuleReference collectionsGenerator = PersistenceFacade.getInstance().createModuleReference("5f9babc9-8d5d-4825-8e61-17b241ee6272()"); final SModuleReference closuresGenerator = PersistenceFacade.getInstance().createModuleReference("857d0a79-6f44-4f46-84ed-9c5b42632011()"); final SModuleReference blInternalGenerator = PersistenceFacade.getInstance().createModuleReference("46ef3033-ce72-4166-b19e-6ceed23b6844()"); final SModuleReference baselangGenerator = PersistenceFacade.getInstance().createModuleReference("985c8c6a-64b4-486d-a91e-7d4112742556()"); final PlanIdentity pi1 = new PlanIdentity("p1"); final PlanIdentity pi2 = new PlanIdentity("p2"); final CheckpointIdentity cp1 = new CheckpointIdentity(pi1, "cp1"); final CheckpointIdentity cp2 = new CheckpointIdentity(pi2, "cp2"); ModelGenerationPlan plan = new ModelAccessHelper(mpsProject.getModelAccess()).runReadAction(new Computable<ModelGenerationPlan>() { @Override public ModelGenerationPlan compute() { Generator closuresGeneratorInstance = (Generator) closuresGenerator.resolve(mpsProject.getRepository()); Generator collectionsGeneratorInstance = (Generator) collectionsGenerator.resolve(mpsProject.getRepository()); Generator blInternalGeneratorInstance = (Generator) blInternalGenerator.resolve(mpsProject.getRepository()); Generator baselangGeneratorInstance = (Generator) baselangGenerator.resolve(mpsProject.getRepository()); final LanguageRegistry languageRegistry = LanguageRegistry.getInstance(mpsProject.getRepository()); Collection<TemplateModule> engagedGenerators = new ArrayList<>(); engagedGenerators.add(((TemplateModule) languageRegistry.getGenerator(closuresGeneratorInstance))); engagedGenerators.add(((TemplateModule) languageRegistry.getGenerator(collectionsGeneratorInstance))); engagedGenerators.add(((TemplateModule) languageRegistry.getGenerator(baselangGeneratorInstance))); engagedGenerators.add(((TemplateModule) languageRegistry.getGenerator(blInternalGeneratorInstance))); RegularPlanBuilder planBuilder = new RegularPlanBuilder(languageRegistry, engagedGenerators); planBuilder.recordCheckpoint(cp1); planBuilder.applyGeneratorWithExtended(closuresGeneratorInstance); planBuilder.synchronizeWithCheckpoint(cp2); planBuilder.applyGeneratorWithExtended(blInternalGeneratorInstance); planBuilder.applyGeneratorWithExtended(baselangGeneratorInstance); return planBuilder.wrapUp(pi1); } }); Assert.assertNotNull(plan); Assert.assertEquals(5, plan.getSteps().size()); myErrors.checkThat(plan.getSteps().get(0), CoreMatchers.instanceOf(Checkpoint.class)); myErrors.checkThat(plan.getSteps().get(1), CoreMatchers.instanceOf(Transform.class)); myErrors.checkThat(plan.getSteps().get(2), CoreMatchers.instanceOf(Checkpoint.class)); myErrors.checkThat(plan.getSteps().get(3), CoreMatchers.instanceOf(Transform.class)); myErrors.checkThat(plan.getSteps().get(4), CoreMatchers.instanceOf(Transform.class)); // Checkpoint p1 = (Checkpoint) plan.getSteps().get(0); Checkpoint p2 = (Checkpoint) plan.getSteps().get(2); myErrors.checkThat(p1.isPersisted(), CoreMatchers.equalTo(true)); myErrors.checkThat(p2.isPersisted(), CoreMatchers.equalTo(false)); Transform t1 = (Transform) plan.getSteps().get(1); // closures + extensions Transform t2 = (Transform) plan.getSteps().get(3); // blInternal + extensions Transform t3 = (Transform) plan.getSteps().get(4); // bl + extensions myErrors.checkThat(t1.getTransformations().size(), CoreMatchers.equalTo(7)); // 2 from closures + 5 from collections myErrors.checkThat(t2.getTransformations().size(), CoreMatchers.equalTo(1)); // 1 MC in blInternal. myErrors.checkThat(t3.getTransformations().size(), CoreMatchers.equalTo(8)); // 3 from BL + 5 from collections } // utility to obtain generators of j.m.g.test.crossmodel.property language private static List<TemplateMappingConfiguration> getCrossmodelPropertyGenerators() { return getGenerators(MetaAdapterFactory.getLanguage(0xdc1cc9486f434687L, 0x90cb17dd5cb27219L, "jetbrains.mps.generator.test.crossmodel.property")); } // utility to obtain generators of j.m.g.test.crossmodel.entity language private static List<TemplateMappingConfiguration> getCrossmodelEntityGenerators() { return getGenerators(MetaAdapterFactory.getLanguage(0x4d14758c3ecb486dL, 0xb8c8ea5beb8ae408L, "jetbrains.mps.generator.test.crossmodel.entity")); } private static List<TemplateMappingConfiguration> getBaseLanguageGenerators() { return getGenerators(MetaAdapterFactory.getLanguage(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, "jetbrains.mps.baseLanguage")); } private static List<TemplateMappingConfiguration> getGenerators(SLanguage language) { final LanguageRegistry lr = LanguageRegistry.getInstance(mpsProject.getRepository()); final GeneratorRuntime g1 = lr.getLanguage(language).getGenerators().iterator().next(); return getGenerators(g1); } private static List<TemplateMappingConfiguration> getGenerators(GeneratorRuntime gr) { ArrayList<TemplateMappingConfiguration> rv = new ArrayList<TemplateMappingConfiguration>(); if (gr instanceof TemplateModule) { for (TemplateModel tm : ((TemplateModule) gr).getModels()) { rv.addAll(tm.getConfigurations()); } } return rv; } private SModel resolve(final SModelReference mr) { Computable<SModel> c = new Computable<SModel>() { @Override public SModel compute() { return mr.resolve(mpsProject.getRepository()); } }; ModelAccess ma = mpsProject.getModelAccess(); if (ma.canRead()) { return c.compute(); } return new ModelAccessHelper(ma).runReadAction(c); } }