Skip to content

Commit

Permalink
Merge pull request #488 from modelix/feature/bulk-sync-git-metadata
Browse files Browse the repository at this point in the history
MODELIX-718 Store Git metadata when syncing
  • Loading branch information
mhuster23 authored Feb 6, 2024
2 parents a5721c8 + 8edc5c4 commit e9e12f1
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 62 deletions.
2 changes: 2 additions & 0 deletions bulk-model-sync-gradle-test/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ modelSync {
url = "http://localhost:28309/v2"
repositoryId = "ci-test"
branchName = "master"
metaProperties["metaKey1"] = "metaValue1"
metaProperties["metaKey2"] = "metaValue2"
}
}
direction("testPull") {
Expand Down
8 changes: 5 additions & 3 deletions bulk-model-sync-gradle-test/ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
set -e
set -x

cd "$(dirname "$0")"
TEST_DIR="$(dirname "$(readlink -f "$0")")"
cd "${TEST_DIR}"

(
cd graph-lang-api
Expand All @@ -20,15 +21,16 @@ if [ "${CI}" != "true" ]; then
}
fi

cd ..
cd "${TEST_DIR}/.."
./gradlew :model-server:run --console=plain --args="-inmemory -port 28309" &
MODEL_SERVER_PID=$!

cd "$(dirname "$0")"
cd "${TEST_DIR}"

curl -X GET --retry 30 --retry-connrefused --retry-delay 1 http://localhost:28309/health

./gradlew runSyncTestPush --console=plain --stacktrace
./gradlew test --tests 'PushTest'
./gradlew test --tests 'ChangeApplier'
./gradlew runSyncTestPull --console=plain --stacktrace
./gradlew test --tests 'PullTest'
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright (c) 2024.
*
* 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 org.modelix.model.sync.bulk.gradle.test

import GraphLang.L_GraphLang
import GraphLang.N_Edge
import GraphLang.N_Node
import GraphLang._C_UntypedImpl_Edge
import GraphLang._C_UntypedImpl_Node
import jetbrains.mps.lang.core.L_jetbrains_mps_lang_core
import kotlinx.coroutines.runBlocking
import org.modelix.metamodel.TypedLanguagesRegistry
import org.modelix.metamodel.typed
import org.modelix.model.ModelFacade
import org.modelix.model.api.ConceptReference
import org.modelix.model.api.getDescendants
import org.modelix.model.client2.ModelClientV2PlatformSpecificBuilder
import org.modelix.model.client2.runWrite
import org.modelix.model.lazy.RepositoryId
import kotlin.test.Test

/**
* Not an actual test. Just a preparation for [PullTest].
* Marking it as a test makes it easily callable from the ci script.
*/
class ChangeApplier {

@Test
fun applyChangesForPullTest() {
val url = "http://0.0.0.0:28309/v2"
val branchRef = ModelFacade.createBranchReference(RepositoryId("ci-test"), "master")
val client = ModelClientV2PlatformSpecificBuilder().url(url).build().apply { runBlocking { init() } }

TypedLanguagesRegistry.register(L_GraphLang)
TypedLanguagesRegistry.register(L_jetbrains_mps_lang_core)

runBlocking {
client.runWrite(branchRef) { rootNode ->
val graphNodes = rootNode
.getDescendants(false)
.filter { it.getConceptReference() == ConceptReference(_C_UntypedImpl_Node.getUID()) }
.map { it.typed<N_Node>() }
.toList()

graphNodes[0].name = "X"
graphNodes[1].name = "Y"
graphNodes[2].name = "Z"

val edges = rootNode
.getDescendants(false)
.filter { it.getConceptReference() == ConceptReference(_C_UntypedImpl_Edge.getUID()) }
.map { it.typed<N_Edge>() }
.toList()

edges[0].source = graphNodes[1]
edges[0].target = graphNodes[3]
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,18 @@

package org.modelix.model.sync.bulk.gradle.test

import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.xmlunit.builder.Input
import org.xmlunit.xpath.JAXPXPathEngine
import java.io.File
import javax.xml.transform.Source
import kotlin.test.assertContentEquals

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class PullTest {
companion object {
private lateinit var source: Source

@JvmStatic
@BeforeAll
fun initSource() {
val localModel = File("build/test-repo/solutions/GraphSolution/models/GraphSolution.example.mps").readText()
source = Input.fromString(localModel).build()
}
}

private val localModel = File("build/test-repo/solutions/GraphSolution/models/GraphSolution.example.mps").readText()
private val source = Input.fromString(localModel).build()

@Test
fun `properties were synced to local`() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,26 @@
package org.modelix.model.sync.bulk.gradle.test

import GraphLang.L_GraphLang
import GraphLang.N_Edge
import GraphLang.N_Node
import GraphLang._C_UntypedImpl_Edge
import GraphLang._C_UntypedImpl_Node
import jetbrains.mps.lang.core.L_jetbrains_mps_lang_core
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Test
import org.modelix.metamodel.TypedLanguagesRegistry
import org.modelix.metamodel.typed
import org.junit.jupiter.api.TestInstance
import org.modelix.model.ModelFacade
import org.modelix.model.api.ConceptReference
import org.modelix.model.api.getDescendants
import org.modelix.model.api.IProperty
import org.modelix.model.api.getRootNode
import org.modelix.model.client2.IModelClientV2
import org.modelix.model.client2.ModelClientV2PlatformSpecificBuilder
import org.modelix.model.client2.getReplicatedModel
import org.modelix.model.client2.runWrite
import org.modelix.model.data.ModelData
import org.modelix.model.data.NodeData
import org.modelix.model.lazy.BranchReference
import org.modelix.model.lazy.RepositoryId
import org.modelix.model.sync.bulk.asExported
import java.io.File
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class PushTest {
private val url = "http://0.0.0.0:28309/v2"
private val branchRef = ModelFacade.createBranchReference(RepositoryId("ci-test"), "master")
private val client = ModelClientV2PlatformSpecificBuilder().url(url).build().apply { runBlocking { init() } }

@Test
fun `nodes were synced to server`() {
Expand All @@ -36,48 +30,26 @@ class PushTest {
val modules = files.map { ModelData.fromJson(it.readText()) }
val inputModel = ModelData(root = NodeData(children = modules.map { it.root }))

TypedLanguagesRegistry.register(L_GraphLang)
TypedLanguagesRegistry.register(L_jetbrains_mps_lang_core)

val repoId = RepositoryId("ci-test")
val branchName = "master"
val url = "http://0.0.0.0:28309/v2"

val branchRef = ModelFacade.createBranchReference(repoId, branchName)
val client = ModelClientV2PlatformSpecificBuilder().url(url).build().apply { runBlocking { init() } }
val replicatedModel = client.getReplicatedModel(branchRef)
val branch = runBlocking { replicatedModel.start() }

branch.runRead {
assertContentEquals(inputModel.root.children, branch.getRootNode().allChildren.map { it.asExported() })
}
replicatedModel.dispose()

applyChangesForPullTest(client, branchRef)
}

private fun applyChangesForPullTest(client: IModelClientV2, branchRef: BranchReference) {
runBlocking {
client.runWrite(branchRef) { rootNode ->
val graphNodes = rootNode
.getDescendants(false)
.filter { it.getConceptReference() == ConceptReference(_C_UntypedImpl_Node.getUID()) }
.map { it.typed<N_Node>() }
.toList()

graphNodes[0].name = "X"
graphNodes[1].name = "Y"
graphNodes[2].name = "Z"

val edges = rootNode
.getDescendants(false)
.filter { it.getConceptReference() == ConceptReference(_C_UntypedImpl_Edge.getUID()) }
.map { it.typed<N_Edge>() }
.toList()
@Test
fun `meta properties were applied to root node`() {
val replicatedModel = client.getReplicatedModel(branchRef)
val branch = runBlocking { replicatedModel.start() }
branch.runRead {
val actual1 = branch.getRootNode().getPropertyValue(IProperty.fromName("metaKey1"))
val actual2 = branch.getRootNode().getPropertyValue(IProperty.fromName("metaKey2"))

edges[0].source = graphNodes[1]
edges[0].target = graphNodes[3]
}
assertEquals("metaValue1", actual1)
assertEquals("metaValue2", actual2)
}
replicatedModel.dispose()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ class ModelSyncGradlePlugin : Plugin<Project> {
it.includedModulePrefixes.set(syncDirection.includedModulePrefixes)
it.continueOnError.set(syncDirection.continueOnError)
it.requestTimeoutSeconds.set(serverTarget.requestTimeoutSeconds)
it.metaProperties.set(serverTarget.metaProperties)
}

project.tasks.register("runSync${syncDirection.name.replaceFirstChar { it.uppercaseChar() }}") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ data class ServerTarget(
override var repositoryId: String? = null,
override var branchName: String? = null,
override var requestTimeoutSeconds: Int = DEFAULT_REQUEST_TIMEOUT_SECONDS,
val metaProperties: MutableMap<String, String> = mutableMapOf(),
) : ServerEndpoint {
override fun getValidationErrors(): List<String> {
val errors = mutableListOf<String>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import kotlinx.coroutines.runBlocking
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.Input
Expand All @@ -29,6 +30,7 @@ import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import org.modelix.model.ModelFacade
import org.modelix.model.api.INode
import org.modelix.model.api.IProperty
import org.modelix.model.api.PNodeAdapter
import org.modelix.model.client2.ModelClientV2
import org.modelix.model.client2.runWrite
Expand Down Expand Up @@ -67,6 +69,9 @@ abstract class ImportIntoModelServer @Inject constructor(of: ObjectFactory) : De
@Input
val requestTimeoutSeconds: Property<Int> = of.property(Int::class.java)

@Input
val metaProperties: MapProperty<String, String> = of.mapProperty(String::class.java, String::class.java)

@TaskAction
fun import() {
val inputDir = inputDir.get().asFile
Expand All @@ -91,6 +96,14 @@ abstract class ImportIntoModelServer @Inject constructor(of: ObjectFactory) : De
logger.info("Got root node: {}", rootNode)
logger.info("Calculating diff...")
ModelImporter(rootNode, continueOnError.get()).importFilesAsRootChildren(files)

logger.info("Setting meta properties...")
for ((key, value) in metaProperties.get()) {
val property = IProperty.fromName(key)
if (rootNode.getPropertyValue(property) == null) {
rootNode.setPropertyValue(property, value)
}
}
}
logger.info("Sending diff to server...")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,19 @@ If the target branch does not exist on the model-server, it will be created.

|`revision`
|String
|Source model-server revision. Can be used instead of `repositoryId` and `branchName`. Only available in ServerSource.
|Source model-server revision. Can be used instead of `repositoryId` and `branchName`.
Only available in ServerSource.

|`requestTimeoutSeconds`
|Integer
|The request timeout measured in seconds to apply when performing HTTP requests towards the model-server. Default: 5 minutes
|The request timeout measured in seconds to apply when performing HTTP requests towards the model-server.
Default: 5 minutes

|`metaProperties`
|MutableMap<String, String>
|Custom properties that will be attached to the root node.
The mapping is `propertyName -> propertyValue`.
Only available in ServerTarget.

|===

Expand Down

0 comments on commit e9e12f1

Please sign in to comment.