Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4d1d018
created stub and test
franciscolopezsancho Sep 24, 2021
99c870b
first test passing
franciscolopezsancho Sep 26, 2021
f5cd830
following suggestions
franciscolopezsancho Sep 27, 2021
ff352a7
following review suggestion
franciscolopezsancho Sep 27, 2021
0010b1c
added ActionTestkitGenerator
franciscolopezsancho Sep 28, 2021
d0edaef
changing order expected,actualValue
franciscolopezsancho Sep 28, 2021
df0528a
Codegen already functional
franciscolopezsancho Oct 4, 2021
28a9b8a
Added test example generation
franciscolopezsancho Oct 4, 2021
f56765a
Merge branch 'main' of github.com:franciscolopezsancho/akkaserverless…
franciscolopezsancho Oct 4, 2021
91c01c0
adding docs
franciscolopezsancho Oct 4, 2021
742af2c
adding FIXME
franciscolopezsancho Oct 4, 2021
f240b9d
formatting
franciscolopezsancho Oct 5, 2021
1dd9b12
adding tag to docs
franciscolopezsancho Oct 5, 2021
b862130
Merge branch 'main' of github.com:franciscolopezsancho/akkaserverless…
franciscolopezsancho Oct 5, 2021
ee4f266
adding missing import
franciscolopezsancho Oct 5, 2021
e79b1c3
changing selectInput/Output from ModelBuilder.Commands
franciscolopezsancho Oct 5, 2021
75deb3d
Update docs/src/modules/java/pages/actions.adoc
franciscolopezsancho Oct 5, 2021
e417a83
Update docs/src/modules/java/pages/actions.adoc
franciscolopezsancho Oct 5, 2021
b221a9e
Update docs/src/modules/java/pages/actions.adoc
franciscolopezsancho Oct 5, 2021
997678f
Update samples/java-eventsourced-counter/src/test/java/com/example/ac…
franciscolopezsancho Oct 5, 2021
1fa4764
adding codegen for RPCs streaming, client and server
franciscolopezsancho Oct 5, 2021
fb1ee72
adding comment after imports
franciscolopezsancho Oct 5, 2021
f4bb328
Update docs/src/modules/java/pages/actions.adoc
franciscolopezsancho Oct 6, 2021
c65d83e
Update docs/src/modules/java/pages/actions.adoc
franciscolopezsancho Oct 6, 2021
c0b2e8a
Update docs/src/modules/java/pages/actions.adoc
franciscolopezsancho Oct 6, 2021
10a7894
Update docs/src/modules/java/pages/actions.adoc
franciscolopezsancho Oct 6, 2021
eee315b
adding suggestions
franciscolopezsancho Oct 6, 2021
ba010e0
Update docs/src/modules/java/pages/actions.adoc
franciscolopezsancho Oct 6, 2021
34524db
Update docs/src/modules/java/pages/actions.adoc
franciscolopezsancho Oct 6, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -508,5 +508,4 @@ object ModelBuilder {
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* Copyright 2021 Lightbend Inc.
*
* 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 com.lightbend.akkasls.codegen
package java

import com.google.common.base.Charsets
import _root_.java.nio.file.{ Files, Path }

object ActionTestKitGenerator {
import com.lightbend.akkasls.codegen.SourceGeneratorUtils._

def generate(
service: ModelBuilder.ActionService,
testSourceDirectory: Path,
generatedTestSourceDirectory: Path): Iterable[Path] = {
var generatedFiles: Seq[Path] = Vector.empty
val packageName = service.fqn.parent.javaPackage
val className = service.className

val packagePath = packageAsPath(packageName)
val testKitPath = generatedTestSourceDirectory.resolve(packagePath.resolve(className + "TestKit.java"))
testKitPath.getParent.toFile.mkdirs()
val sourceCode = generateSourceCode(service, packageName, className)
Files.write(testKitPath, sourceCode.getBytes(Charsets.UTF_8))
generatedFiles :+= testKitPath

generatedFiles
}

private[codegen] def generateSourceCode(
service: ModelBuilder.ActionService,
packageName: String,
className: String): String = {

val imports = generateImports(
commandTypes(service.commands),
"",
otherImports = Seq(
"java.util.ArrayList",
"java.util.List",
"java.util.function.Function",
"java.util.Optional",
s"$packageName.$className",
"com.akkaserverless.javasdk.action.Action",
"com.akkaserverless.javasdk.action.ActionCreationContext",
"com.akkaserverless.javasdk.testkit.ActionResult",
"com.akkaserverless.javasdk.testkit.impl.ActionResultImpl",
"com.akkaserverless.javasdk.impl.action.ActionEffectImpl",
"com.akkaserverless.javasdk.testkit.impl.StubActionCreationContext",
"com.akkaserverless.javasdk.testkit.impl.StubActionContext"))

val testKitClassName = s"${className}TestKit"

s"""$managedComment
|package ${service.fqn.parent.javaPackage};
|
|$imports
|
|public final class $testKitClassName {
|
| private Function<ActionCreationContext, $className> actionFactory;
|
| private $className createAction() {
| $className action = actionFactory.apply(new StubActionCreationContext());
| action._internalSetActionContext(Optional.of(new StubActionContext()));
| return action;
| };
|
| public static $testKitClassName of(Function<ActionCreationContext, $className> actionFactory) {
| return new $testKitClassName(actionFactory);
| }
|
| private $testKitClassName(Function<ActionCreationContext, $className> actionFactory) {
| this.actionFactory = actionFactory;
| }
|
| private <E> ActionResult<E> interpretEffects(Action.Effect<E> effect) {
| return new ActionResultImpl(effect);
| }
|
| ${Format.indent(generateServices(service), 2)}
|
|}
|""".stripMargin
}

def generateServices(service: ModelBuilder.ActionService): String = {
require(!service.commands.isEmpty, "empty `commands` not allowed")

def selectOutput(command: ModelBuilder.Command): String = {
val parent = command.outputType.parent
s"${parent.javaPackageOption.getOrElse(parent.protoPackage)}.${filterJavaOuterClassname(
parent.javaOuterClassnameOption)}${command.outputType.name}"

}

def selectInput(command: ModelBuilder.Command): String = {
val parent = command.inputType.parent
s"${parent.javaPackageOption.getOrElse(parent.protoPackage)}.${filterJavaOuterClassname(
parent.javaOuterClassnameOption)}${command.inputType.name}"

}

def filterJavaOuterClassname(javaOuterClassnameOption: Option[String]): String = {
javaOuterClassnameOption match {
case None => ""
case Some(value) =>
if (value == "EmptyProto" || value == "AnyProto") ""
else value + "."
}
}

service.commands
.map { command =>
s"""|public ActionResult<${selectOutput(command)}> ${lowerFirst(command.name)}(${selectInput(
command)} ${lowerFirst(command.inputType.protoName)}) {
| Action.Effect<${selectOutput(command)}> effect = createAction().${lowerFirst(command.name)}(${lowerFirst(
command.inputType.protoName)});
| return interpretEffects(effect);
|}
|""".stripMargin + "\n"
}
.mkString("")
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ object SourceGenerator {
integrationTestSourceDirectory,
generatedSourceDirectory)
case service: ModelBuilder.ActionService =>
ActionServiceSourceGenerator.generate(service, sourceDirectory, generatedSourceDirectory)
ActionServiceSourceGenerator.generate(service, sourceDirectory, generatedSourceDirectory) ++
ActionTestKitGenerator.generate(service, testSourceDirectory, generatedTestSourceDirectory)
case _ => Seq.empty
} ++ {
MainSourceGenerator.generate(
Expand Down
Binary file not shown.
15 changes: 15 additions & 0 deletions codegen/java-gen/src/test/resources/logback-test.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%date{ISO8601} %-5level %logger - %msg%n</pattern>
</encoder>
</appender>

<logger name="akka" level="INFO"/>
<logger name="com.akkaserverless" level="INFO"/>
<logger name="com.lightbend.akkasls.codegen" level="INFO"/>

<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
* Copyright 2021 Lightbend Inc.
*
* 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 com.lightbend.akkasls.codegen
package java

import com.google.protobuf.DescriptorProtos.FileDescriptorSet
import com.google.protobuf.DescriptorProtos
import com.google.protobuf.Descriptors

import _root_.java.io.FileInputStream
import _root_.java.nio.file.Paths
import scala.jdk.CollectionConverters._
import scala.util.Using
import com.google.protobuf.ExtensionRegistry
import org.slf4j.LoggerFactory

import scala.collection.mutable
import scala.util.Try

class ActionTestKitGeneratorSuite extends munit.FunSuite {
private val testData = TestData.javaStyle

val log = LoggerFactory.getLogger(getClass)

test(
"it can generate an specific TestKit for the user-function file " +
"in test/resources/descriptor-sets build from the proto files" +
"from samples/java-eventsourced-counter") {

val packageName = "com.example.actions"
val model = generateModel
val service: ModelBuilder.ActionService = {
model.get.services("com.example.actions.CounterJournalToTopic").asInstanceOf[ModelBuilder.ActionService]
}
val className = service.className

//This prints the contents of the model, services or entity. It's necessary to
// see how the user-function.desc (see generateModel below) maps to ModelBuilder objects.
log.debug(munit.Assertions.munitPrint(service))
Comment thread
franciscolopezsancho marked this conversation as resolved.
Outdated

val sourceCode = ActionTestKitGenerator.generateSourceCode(service, packageName, className)

val expected =
"""|/* This code is managed by Akka Serverless tooling.
| * It will be re-generated to reflect any changes to your protobuf definitions.
| * DO NOT EDIT
| */
|package com.example.actions;
|
|import com.akkaserverless.javasdk.action.Action;
|import com.akkaserverless.javasdk.action.ActionCreationContext;
|import com.akkaserverless.javasdk.impl.action.ActionEffectImpl;
|import com.akkaserverless.javasdk.testkit.ActionResult;
|import com.akkaserverless.javasdk.testkit.impl.ActionResultImpl;
|import com.akkaserverless.javasdk.testkit.impl.StubActionContext;
|import com.akkaserverless.javasdk.testkit.impl.StubActionCreationContext;
|import com.example.actions.CounterJournalToTopicAction;
|import com.example.actions.CounterTopicApi;
|import com.example.domain.CounterDomain;
|import com.google.protobuf.Any;
|import com.google.protobuf.Empty;
|import java.util.ArrayList;
|import java.util.List;
|import java.util.Optional;
|import java.util.function.Function;
|
|public final class CounterJournalToTopicActionTestKit {
|
| private Function<ActionCreationContext, CounterJournalToTopicAction> actionFactory;
|
| private CounterJournalToTopicAction createAction() {
| CounterJournalToTopicAction action = actionFactory.apply(new StubActionCreationContext());
| action._internalSetActionContext(Optional.of(new StubActionContext()));
| return action;
| };
|
| public static CounterJournalToTopicActionTestKit of(Function<ActionCreationContext, CounterJournalToTopicAction> actionFactory) {
| return new CounterJournalToTopicActionTestKit(actionFactory);
| }
|
| private CounterJournalToTopicActionTestKit(Function<ActionCreationContext, CounterJournalToTopicAction> actionFactory) {
| this.actionFactory = actionFactory;
| }
|
| private <E> ActionResult<E> interpretEffects(Action.Effect<E> effect) {
| return new ActionResultImpl(effect);
| }
|
| public ActionResult<com.example.actions.CounterTopicApi.Increased> increase(com.example.domain.CounterDomain.ValueIncreased valueIncreased) {
| Action.Effect<com.example.actions.CounterTopicApi.Increased> effect = createAction().increase(valueIncreased);
| return interpretEffects(effect);
| }
|
| public ActionResult<com.example.actions.CounterTopicApi.Decreased> decrease(com.example.domain.CounterDomain.ValueDecreased valueDecreased) {
| Action.Effect<com.example.actions.CounterTopicApi.Decreased> effect = createAction().decrease(valueDecreased);
| return interpretEffects(effect);
| }
|
| public ActionResult<com.google.protobuf.Empty> ignore(com.google.protobuf.Any any) {
| Action.Effect<com.google.protobuf.Empty> effect = createAction().ignore(any);
| return interpretEffects(effect);
| }
|
|}""".stripMargin

assertNoDiff(sourceCode, expected)
}

test("it can generate an specific Test stub for the entity") {}

/**
* This ModelBuilder.EventSourcedEntity is equivalent to the entity in
* test/resources/testkit/shoppingcart_domain.proto
*/
def generateModel(): Try[ModelBuilder.Model] = {

Comment thread
franciscolopezsancho marked this conversation as resolved.
val log = LoggerFactory.getLogger(getClass)
implicit val codegenLog = new Log {
override def debug(message: String): Unit = log.debug(message)
override def info(message: String): Unit = log.info(message)
}
implicit val e = TestFullyQualifiedNameExtractor

val testFilesPath = Paths.get(getClass.getClassLoader.getResource("descriptor-sets").toURI)
val descriptorFilePath =
testFilesPath.resolve("java-eventsourced-counter-user-function.desc")

val registry = ExtensionRegistry.newInstance()
registry.add(com.akkaserverless.Annotations.service)
registry.add(com.akkaserverless.Annotations.file)

Using(new FileInputStream(descriptorFilePath.toFile)) { fis =>
val fileDescSet = FileDescriptorSet.parseFrom(fis, registry)
val fileList = fileDescSet.getFileList.asScala

val descriptors: mutable.Seq[Descriptors.FileDescriptor] =
fileList.foldLeft(Array[Descriptors.FileDescriptor]())((acc, file) => accumulatedBuildFrom(acc, file))

val model = ModelBuilder.introspectProtobufClasses(descriptors)
model
}

}

private def accumulatedBuildFrom(
fileDescriptors: Array[Descriptors.FileDescriptor],
file: DescriptorProtos.FileDescriptorProto): Array[Descriptors.FileDescriptor] = {
fileDescriptors ++ List(Descriptors.FileDescriptor.buildFrom(file, fileDescriptors.toArray, true))
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ class SourceGeneratorSuite extends munit.FunSuite {
generatedSourceDirectory.resolve("com/example/service/AbstractMyService5Action.java"),
generatedSourceDirectory.resolve("com/example/service/MyService5ActionProvider.java"),
generatedSourceDirectory.resolve("com/example/service/MyService5ActionHandler.java"),
generatedTestSourceDirectory.resolve("com/example/service/MyService5ActionTestKit.java"),
sourceDirectory.resolve("com/example/service/domain/MyReplicatedEntity6.java"),
generatedSourceDirectory.resolve("com/example/service/domain/AbstractMyReplicatedEntity6.java"),
generatedSourceDirectory.resolve("com/example/service/domain/MyReplicatedEntity6Handler.java"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.example.actions;

import com.akkaserverless.javasdk.action.Action;
import com.akkaserverless.javasdk.impl.action.ActionEffectImpl;
import com.akkaserverless.javasdk.testkit.ActionResult;
import com.example.actions.CounterTopicApi;
import com.example.actions.CounterJournalToTopicAction;
import com.google.protobuf.Empty;
import org.junit.Test;
import com.example.actions.CounterJournalToTopicActionTestKit;
import com.example.domain.CounterDomain;

import static org.junit.Assert.*;

public class CounterJournalToTopicActionTest {

@Test
public void increaseTest() {
CounterJournalToTopicActionTestKit testKit = CounterJournalToTopicActionTestKit.of(CounterJournalToTopicAction::new);
int valueToincrease = 1;
ActionResult<CounterTopicApi.Increased> result = testKit.increase(CounterDomain.ValueIncreased.newBuilder().setValue(valueToincrease).build());
assertTrue(result.isReply());
CounterTopicApi.Increased replyMessage = result.getReply();
assertEquals(valueToincrease,replyMessage.getValue());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public interface ActionContext extends MetadataContext {
*
* @return The call level metadata.
*/
Metadata metadata();
Metadata metadata(); // TODO this is not necessary
Comment thread
franciscolopezsancho marked this conversation as resolved.
Outdated

/**
* The origin subject of the {@link CloudEvent}. For example, the entity key when the event was
Expand Down
Loading