1
1
package org.modelix.mps.sync3
2
2
3
3
import com.intellij.configurationStore.saveSettings
4
- import com.intellij.openapi.util.use
5
4
import com.intellij.openapi.application.ApplicationInfo
6
5
import io.ktor.client.HttpClient
7
6
import io.ktor.client.engine.cio.CIO
8
7
import io.ktor.client.plugins.ResponseException
9
8
import io.ktor.client.request.get
10
9
import io.ktor.http.HttpStatusCode
10
+ import jetbrains.mps.core.tool.environment.util.SetLibraryContributor
11
+ import jetbrains.mps.ide.MPSCoreComponents
11
12
import jetbrains.mps.ide.project.ProjectHelper
13
+ import jetbrains.mps.library.contributor.LibDescriptor
14
+ import jetbrains.mps.smodel.MPSModuleRepository
15
+ import jetbrains.mps.smodel.SNodePointer
12
16
import jetbrains.mps.smodel.SNodeUtil
13
17
import jetbrains.mps.smodel.adapter.ids.SConceptId
14
18
import jetbrains.mps.smodel.adapter.ids.SContainmentLinkId
15
19
import jetbrains.mps.smodel.adapter.structure.concept.SConceptAdapterById
16
20
import jetbrains.mps.smodel.adapter.structure.link.SContainmentLinkAdapterById
21
+ import jetbrains.mps.vfs.VFSManager
17
22
import kotlinx.coroutines.delay
18
23
import org.jetbrains.mps.openapi.model.SNode
19
24
import org.jetbrains.mps.openapi.persistence.PersistenceFacade
@@ -26,6 +31,7 @@ import org.modelix.model.IVersion
26
31
import org.modelix.model.api.BuiltinLanguages
27
32
import org.modelix.model.api.INodeReference
28
33
import org.modelix.model.api.getDescendants
34
+ import org.modelix.model.api.getName
29
35
import org.modelix.model.client2.ModelClientV2
30
36
import org.modelix.model.client2.computeWriteOnModel
31
37
import org.modelix.model.client2.runWriteOnModel
@@ -39,15 +45,18 @@ import org.modelix.model.lazy.RepositoryId
39
45
import org.modelix.model.mpsadapters.MPSModuleAsNode
40
46
import org.modelix.model.mpsadapters.MPSProjectReference
41
47
import org.modelix.model.mpsadapters.MPSProperty
48
+ import org.modelix.model.mpsadapters.computeRead
42
49
import org.modelix.model.mpsadapters.toModelix
43
50
import org.modelix.model.mutable.asModelSingleThreaded
44
51
import org.modelix.model.oauth.IAuthConfig
45
52
import org.modelix.model.operations.IOperation
53
+ import org.modelix.model.operations.SetPropertyOp
46
54
import org.modelix.model.server.ModelServerPermissionSchema
47
- import org.modelix.mps.api.ModelixMpsApi
48
55
import org.modelix.mps.multiplatform.model.MPSIdGenerator
56
+ import org.modelix.mps.multiplatform.model.MPSNodeReference
49
57
import org.modelix.mps.sync3.ui.OpenFrontendAction
50
58
import org.modelix.streams.getBlocking
59
+ import java.io.File
51
60
import java.net.URL
52
61
import java.nio.file.Path
53
62
import java.util.concurrent.atomic.AtomicLong
@@ -530,6 +539,81 @@ class ProjectSyncTest : MPSTestBase() {
530
539
assertEquals(version1.getContentHash(), version2.getContentHash())
531
540
}
532
541
542
+ private suspend fun <R > withGlobalLibrary (folder : File , body : suspend () -> R ): R {
543
+ val vfsManager = MPSCoreComponents .getInstance().platform.findComponent(VFSManager ::class .java)!!
544
+ val packagedModulesFolder = vfsManager.getFileSystem(VFSManager .FILE_FS ).getFile(folder)
545
+ val packagedModulesContributors = listOf (SetLibraryContributor .fromSet(" packaged-modules" , setOf (LibDescriptor (packagedModulesFolder))))
546
+ MPSCoreComponents .getInstance().libraryInitializer.load(packagedModulesContributors)
547
+ try {
548
+ return body()
549
+ } finally {
550
+ MPSCoreComponents .getInstance().libraryInitializer.unload(packagedModulesContributors)
551
+ }
552
+ }
553
+
554
+ fun `test change in packaged module` (): Unit = runWithModelServer { port ->
555
+ // Modules in packaged-modules/modules.jar are the same as in solutions/ but packaged into a jar file
556
+ // which makes them read-only.
557
+
558
+ val branchRef = RepositoryId (" sync-test" ).getBranchReference(" branchA" )
559
+ val changedNodeRef = SNodePointer .deserialize(" r:cd78e6ac-0e34-490a-9b49-e5643f948d6d(NewSolution.a_model)/8281020627045237343" )
560
+ val version1 = withGlobalLibrary(File (" testdata/with-packaged-module/packaged-modules" )) {
561
+ val globalRepo = MPSModuleRepository .getInstance()
562
+ globalRepo.modelAccess.runReadAction {
563
+ assertContains(globalRepo.modules.map { it.moduleName.orEmpty() }.filterNot { it.startsWith(" jetbrains." ) }, " NewSolution" )
564
+ }
565
+
566
+ openTestProject(" with-packaged-module" ).also { p ->
567
+ val mpsProject = ProjectHelper .fromIdeaProject(p)!!
568
+ mpsProject.modelAccess.runReadAction {
569
+ val changedNode = checkNotNull(changedNodeRef.resolve(mpsProject.repository)) {
570
+ " Node not found: $changedNodeRef "
571
+ }
572
+ assertEquals(" MyClass" , changedNode.name)
573
+ assertEquals(" NewSolution.a_model" , changedNode.model?.name?.value)
574
+ assertTrue(" Not read-only: ${changedNode.model?.name} " , changedNode.model?.isReadOnly == true )
575
+ }
576
+ p.close()
577
+ }
578
+
579
+ syncProjectToServer(" with-packaged-module" , port, branchRef)
580
+ }
581
+
582
+ delay(1 .seconds) // wait for the modules to be unregistered
583
+
584
+ val version2 = syncProjectToServer(" with-packaged-module-changed" , port, branchRef, version1.getContentHash())
585
+
586
+ // read-only modules should be skipped
587
+ assertNotEquals(version1.getContentHash(), version2.getContentHash())
588
+ version2 as CLVersion
589
+ val operation = version2.operations.single() as SetPropertyOp
590
+ assertEquals(" MyClassRenamed" , operation.value)
591
+
592
+ val branchRef2 = RepositoryId (" sync-test" ).getBranchReference(" branchB" )
593
+ val client = ModelClientV2 .builder().url(" http://localhost:$port " ).lazyAndBlockingQueries().build()
594
+ client.push(branchRef2, version1, null )
595
+
596
+ withGlobalLibrary(File (" testdata/with-packaged-module/packaged-modules" )) {
597
+ openTestProject(" with-packaged-module" )
598
+ val binding = IModelSyncService .getInstance(mpsProject)
599
+ .addServer(" http://localhost:$port " )
600
+ .bind(branchRef, null )
601
+ binding.flush()
602
+
603
+ suspend fun nameOnServer () = client.pull(branchRef2, null ).getModelTree().asModelSingleThreaded().resolveNode(MPSNodeReference .parseSNodeReference(changedNodeRef.toString())).getName()
604
+ fun nameInMPS () = mpsProject.repository.computeRead { changedNodeRef.resolve(mpsProject.repository)!! .name }
605
+
606
+ assertEquals(" MyClass" , nameInMPS())
607
+ MPSNodeReference .parseSNodeReference(changedNodeRef.toString())
608
+ assertEquals(" MyClass" , nameOnServer())
609
+ client.push(branchRef2, version2, null )
610
+ assertEquals(" MyClassRenamed" , nameOnServer())
611
+ binding.flush()
612
+ assertEquals(" MyClass" , nameInMPS()) // name remains unchanged, because the model is read-only
613
+ assertEquals(" MyClassRenamed" , nameOnServer())
614
+ }
615
+ }
616
+
533
617
fun `test missing permission on initial sync is detected` (): Unit = runWithModelServer(hmacKey = " abc" ) { port ->
534
618
val branchRef = RepositoryId (" sync-test" ).getBranchReference(" branchA" )
535
619
AppLevelModelSyncService .getInstance().getOrCreateConnection(
0 commit comments