Skip to content

Commit

Permalink
fix(mps-legacy-sync-plugin): fix initialization of ModelSyncService
Browse files Browse the repository at this point in the history
 Initialization of `ModelSyncService` initialized `org.modelix.model.mpsplugin.plugin.ApplicationPlugin_AppPluginPart`.
During initialization of `ApplicationPlugin_AppPluginPart` in some cases `ModelSyncService` was accessed.
 Such a case was when `EModelixExecutionMode.PROJECTOR` was used.
 In such cases, initialization of `ApplicationPlugin_AppPluginPart` failed because `ModelSyncService` was still initializing.

 Now `ApplicationPlugin_AppPluginPart` is initialized while adding the first project.
  • Loading branch information
Oleksandr Dzhychko committed Apr 11, 2024
1 parent 1809fe9 commit c3e98f7
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 24 deletions.
50 changes: 33 additions & 17 deletions mps-legacy-sync-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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") })
Expand All @@ -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
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,31 +61,39 @@ 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<ServerConnection> = emptyList()
var httpClient: HttpClient? = null

init {
LOG.info("Initializing the model sync service.")
check(INSTANCE == null) { "Single instance expected" }
INSTANCE = this
Mpsplugin_ApplicationPlugin().let {
it.createGroups()
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)
}

Expand Down
50 changes: 50 additions & 0 deletions mps-legacy-sync-plugin/src/test/kotlin/ProjectorAutoBindingTest.kt
Original file line number Diff line number Diff line change
@@ -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"])
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit c3e98f7

Please sign in to comment.