diff --git a/mps-legacy-sync-plugin/build.gradle.kts b/mps-legacy-sync-plugin/build.gradle.kts index 5a261c3e..139fd1d7 100644 --- a/mps-legacy-sync-plugin/build.gradle.kts +++ b/mps-legacy-sync-plugin/build.gradle.kts @@ -63,6 +63,7 @@ dependencies { implementation(coreLibs.kotlin.datetime) implementation(coreLibs.kotlin.reflect) + implementation(coreLibs.ktor.server.resources) // There is a usage of MakeActionParameters in ProjectMakeRunner which we might want to delete compileOnly(mpsHome.map { it.files("plugins/mps-make/languages/jetbrains.mps.ide.make.jar") }) @@ -72,18 +73,8 @@ dependencies { testImplementation(libs.modelix.model.server) testImplementation(libs.modelix.authorization) testImplementation(coreLibs.kotlin.reflect) -// implementation(libs.ktor.server.core) -// implementation(libs.ktor.server.cors) -// implementation(libs.ktor.server.netty) -// implementation(libs.ktor.server.html.builder) -// implementation(libs.ktor.server.auth) -// implementation(libs.ktor.server.auth.jwt) -// implementation(libs.ktor.server.status.pages) -// implementation(libs.ktor.server.forwarded.header) testImplementation(coreLibs.ktor.server.websockets) testImplementation(coreLibs.ktor.server.content.negotiation) - implementation(coreLibs.ktor.server.resources) -// implementation(libs.ktor.serialization.json) } // Configure Gradle IntelliJ Plugin @@ -94,19 +85,44 @@ intellij { plugins = listOf("jetbrains.mps.ide.make") } +data class TestPartition(val partitionName: String, val partitionPattern: String) +// Some test need to run in isolation from other tests. +// A test task (Test::class) runs all tests in the same JVM and same MPS instance. +val testClassPartitionsToRunInIsolation = listOf( + // `ProjectorAutoBindingTest` sets system properties to enable `EModelixExecutionMode.PROJECTOR`. + // The system properties should not influence the other tests. + // Also, currently the execution mode is application-specific and not project-specific. + TestPartition("ProjectorAutoBindingTest", "**/ProjectorAutoBindingTest.class"), +) + +// Tests currently fail for these versions because of some deadlock. +// The deadlock does not seem to be caused by our test code. +// Even an unmodified `HeavyPlatformTestCase` hangs. +val enableTests = !setOf(212, 213, 222).contains(mpsPlatformVersion) + tasks { patchPluginXml { - sinceBuild.set("211") // 203 not supported, because VersionFixer was replaced by ModuleDependencyVersions in 211 + sinceBuild.set("211") // 203 is not supported, because VersionFixer was replaced by ModuleDependencyVersions in 211 untilBuild.set("232.10072.781") } test { - // tests currently fail for these versions - enabled = !setOf( - 212, // timeout because of some deadlock - 213, // timeout because of some deadlock - 222, // timeout because of some deadlock - ).contains(mpsPlatformVersion) + enabled = enableTests + useJUnit { + setExcludes(testClassPartitionsToRunInIsolation.map { it.partitionPattern }) + } + } + + for (testClassToRunInIsolation in testClassPartitionsToRunInIsolation) { + val testTask = register("test${testClassToRunInIsolation.partitionName}", Test::class) { + enabled = enableTests + useJUnit { + include(testClassToRunInIsolation.partitionPattern) + } + } + check { + dependsOn(testTask) + } } buildSearchableOptions { diff --git a/mps-legacy-sync-plugin/src/main/java/org/modelix/common/InstanceJwtToken.kt b/mps-legacy-sync-plugin/src/main/java/org/modelix/common/InstanceJwtToken.kt index d7e46ae9..6903ae28 100644 --- a/mps-legacy-sync-plugin/src/main/java/org/modelix/common/InstanceJwtToken.kt +++ b/mps-legacy-sync-plugin/src/main/java/org/modelix/common/InstanceJwtToken.kt @@ -2,5 +2,5 @@ package org.modelix.common /*Generated by MPS */ object InstanceJwtToken { - var token: String = System.getenv("INITIAL_JWT_TOKEN") + var token: String? = PropertyOrEnv["INITIAL_JWT_TOKEN"] } diff --git a/mps-legacy-sync-plugin/src/main/kotlin/org/modelix/mps/sync/ModelSyncService.kt b/mps-legacy-sync-plugin/src/main/kotlin/org/modelix/mps/sync/ModelSyncService.kt index 3a5a694e..4c337dd3 100644 --- a/mps-legacy-sync-plugin/src/main/kotlin/org/modelix/mps/sync/ModelSyncService.kt +++ b/mps-legacy-sync-plugin/src/main/kotlin/org/modelix/mps/sync/ModelSyncService.kt @@ -61,14 +61,16 @@ class ModelSyncService : Disposable, ISyncService { var INSTANCE: ModelSyncService? = null } - private val legacyAppPluginParts = listOf( - org.modelix.model.mpsadapters.plugin.ApplicationPlugin_AppPluginPart(), - org.modelix.model.mpsplugin.plugin.ApplicationPlugin_AppPluginPart(), - ) + private val mpsAdaptersLegacyPluginPart = org.modelix.model.mpsadapters.plugin.ApplicationPlugin_AppPluginPart() + + // Initialize the plugin part responsible for syncing only once a project is added. + private var syncPluginLegacyPluginPart: org.modelix.model.mpsplugin.plugin.ApplicationPlugin_AppPluginPart? = null + private var serverConnections: List = emptyList() var httpClient: HttpClient? = null init { + LOG.info("Initializing the model sync service.") check(INSTANCE == null) { "Single instance expected" } INSTANCE = this Mpsplugin_ApplicationPlugin().let { @@ -76,16 +78,22 @@ class ModelSyncService : Disposable, ISyncService { it.adjustRegularGroups() } - legacyAppPluginParts.forEach { it.init() } + mpsAdaptersLegacyPluginPart.init() } override fun dispose() { INSTANCE = null // serverConnections disposal is handled by Disposer - legacyAppPluginParts.forEach { it.dispose() } + mpsAdaptersLegacyPluginPart.dispose() + syncPluginLegacyPluginPart?.dispose() } fun registerProject(project: com.intellij.openapi.project.Project) { + if (syncPluginLegacyPluginPart == null) { + syncPluginLegacyPluginPart = org.modelix.model.mpsplugin.plugin.ApplicationPlugin_AppPluginPart().also { + it.init() + } + } project.getService(ModelSyncProjectService::class.java) } diff --git a/mps-legacy-sync-plugin/src/main/kotlin/org/modelix/mps/sync/api/ISyncService.kt b/mps-legacy-sync-plugin/src/main/kotlin/org/modelix/mps/sync/api/ISyncService.kt index e3ee582b..68f62150 100644 --- a/mps-legacy-sync-plugin/src/main/kotlin/org/modelix/mps/sync/api/ISyncService.kt +++ b/mps-legacy-sync-plugin/src/main/kotlin/org/modelix/mps/sync/api/ISyncService.kt @@ -64,9 +64,12 @@ interface ISyncService { fun findMpsNode(cloudNodeReference: INodeReference): List /** - * Synchronize all bindings between MPS and the model server. + * Synchronize all bindings between MPS and the local Modelix branch. * The call blocks until all synchronizations are finished or timeout. * + * It is not guaranteed that after invocation, the local Modelix branch has been synced to the model server. + * Synchronizing to the model server happens asynchronously in the background. + * * The synchronization is always scheduled on a different thread which will try to acquire a write and read lock. * To not deadlock your program, do not call this method while holding a write lock or read lock. */ diff --git a/mps-legacy-sync-plugin/src/test/kotlin/ProjectorAutoBindingTest.kt b/mps-legacy-sync-plugin/src/test/kotlin/ProjectorAutoBindingTest.kt new file mode 100644 index 00000000..8306f97a --- /dev/null +++ b/mps-legacy-sync-plugin/src/test/kotlin/ProjectorAutoBindingTest.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023. + * + * 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. + */ + +import org.modelix.model.client2.ModelClientV2 +import org.modelix.model.mpsplugin.ModelServerConnections +import org.modelix.model.mpsplugin.plugin.EModelixExecutionMode +import org.modelix.model.mpsplugin.plugin.ModelixConfigurationSystemProperties + +class ProjectorAutoBindingTest : SyncPluginTestBase("projectWithOneEmptyModel") { + fun `test project binds project when in with execution mode is PROJECTOR`() { + System.setProperty("MODEL_URI", "http://localhost") + System.setProperty("REPOSITORY_ID", "default") + System.setProperty( + ModelixConfigurationSystemProperties.EXECUTION_MODE_SYSPROP, + EModelixExecutionMode.PROJECTOR.toString(), + ) + + runTestWithSyncService { + val modelClient = ModelClientV2.builder().url(baseUrl).client(httpClient).build() + modelClient.init() + delayUntil(exceptionMessage = "Failed to auto connect to the model server.") { + ModelServerConnections.instance.modelServers.isNotEmpty() + } + assertTrue(ModelServerConnections.instance.modelServers.size == 1) + delayUntil(exceptionMessage = "Failed to sync to model server.") { + val branches = modelClient.listBranches(defaultBranchRef.repositoryId) + branches.isNotEmpty() + } + + val dataOnServer = readDumpFromServer(defaultBranchRef) + val project = dataOnServer.children.single() + val moduleData = project.children.single() + val model = moduleData.children.single { it.role == "models" } + assertEquals("aSolution.aModel", model.properties["name"]) + } + } +} diff --git a/mps-legacy-sync-plugin/src/test/kotlin/ReferenceSynchronisationTest.kt b/mps-legacy-sync-plugin/src/test/kotlin/ReferenceSynchronisationTest.kt index b19caeb5..e482871d 100644 --- a/mps-legacy-sync-plugin/src/test/kotlin/ReferenceSynchronisationTest.kt +++ b/mps-legacy-sync-plugin/src/test/kotlin/ReferenceSynchronisationTest.kt @@ -90,7 +90,12 @@ class ReferenceSynchronisationTest : SyncPluginTestBase("projectWithReferences") null, ) } - syncService.flushAllBindings() + delayUntil(exceptionMessage = "Failed to sync module to model server") { + val dataOnServer = readDumpFromServer(defaultBranchRef) + dataOnServer.children.any { module -> + module.children.any { it.role == "models" } + } + } // Assert val rootNodeData = readDumpFromServer(defaultBranchRef) diff --git a/mps-legacy-sync-plugin/src/test/kotlin/SyncPluginTestBase.kt b/mps-legacy-sync-plugin/src/test/kotlin/SyncPluginTestBase.kt index 6da8c88e..8136e5d3 100644 --- a/mps-legacy-sync-plugin/src/test/kotlin/SyncPluginTestBase.kt +++ b/mps-legacy-sync-plugin/src/test/kotlin/SyncPluginTestBase.kt @@ -70,6 +70,7 @@ abstract class SyncPluginTestBase(private val testDataName: String?) : HeavyPlat suspend fun delayUntil( checkIntervalMilliseconds: Long = 1000, timeoutMilliseconds: Long = 30_000, + exceptionMessage: String? = "Waited too long.", condition: suspend () -> Boolean, ) { check(checkIntervalMilliseconds > 0) {