Skip to content

Commit

Permalink
chore: add round-trip tests for code-generation (Java) (#753)
Browse files Browse the repository at this point in the history
See the new README.md for a description.
  • Loading branch information
jrudolph authored Dec 14, 2021
1 parent c231d5b commit b41ec26
Show file tree
Hide file tree
Showing 15 changed files with 617 additions and 1 deletion.
10 changes: 10 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,16 @@ lazy val codegenJava =
.configs(IntegrationTest)
.dependsOn(codegenCore % "compile->compile;test->test")
.enablePlugins(PublishSonatype)
.settings(
// to provide access to protoc to tests
Test / buildInfoPackage := "com.lightbend.akkasls.codegen.java",
Test / buildInfoKeys := Seq(
BuildInfoKey(PB.protocExecutable),
BuildInfoKey(codegenCore / PB.externalIncludePath),
BuildInfoKey(codegenCore / PB.externalSourcePath),
BuildInfoKey(Test / resourceDirectory)))
// only need BuildInfo in Test scope so some manual setup here
.settings(BuildInfoPlugin.buildInfoScopedSettings(Test) ++ BuildInfoPlugin.buildInfoDefaultSettings)
.settings(common)
.settings(Defaults.itSettings)
.settings(name := "akkaserverless-codegen-java", testFrameworks += new TestFramework("munit.Framework"))
Expand Down
13 changes: 13 additions & 0 deletions codegen/java-gen/src/test/resources/tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Round-trip tests for code generation

These are the test files for the `ExampleSuite`.

Each directory contains one test case.

The `proto` below each test directory contains the input protobuf files and the `generated-` directories contain the
expected output files. You can create a file named `regenerate` into the test folder to have the test succeed and
create the test files for the given protobuf definitions. The `regenerate` file will be automatically deleted afterwards.

You can also change the flag `regenerateAll` in `ExampleSuite` to have the full set of example files regenerated after
a change. In that case, the test will always succeed and you are required to validate the diff to the previous version
of files.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.example.service;

import akka.NotUsed;
import akka.stream.javadsl.Source;
import com.google.protobuf.Empty;
import org.example.Components;
import org.example.ComponentsImpl;

// This code is managed by Akka Serverless tooling.
// It will be re-generated to reflect any changes to your protobuf definitions.
// DO NOT EDIT

/** An action. */
public abstract class AbstractMyServiceAction extends com.akkaserverless.javasdk.action.Action {

protected final Components components() {
return new ComponentsImpl(actionContext());
}

/** Handler for "simpleMethod". */
public abstract Effect<Empty> simpleMethod(ServiceOuterClass.MyRequest myRequest);

/** Handler for "streamedOutputMethod". */
public abstract Source<Effect<Empty>, NotUsed> streamedOutputMethod(ServiceOuterClass.MyRequest myRequest);

/** Handler for "streamedInputMethod". */
public abstract Effect<Empty> streamedInputMethod(Source<ServiceOuterClass.MyRequest, NotUsed> myRequestSrc);

/** Handler for "fullStreamedMethod". */
public abstract Source<Effect<Empty>, NotUsed> fullStreamedMethod(Source<ServiceOuterClass.MyRequest, NotUsed> myRequestSrc);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.example.service;

import com.akkaserverless.javasdk.action.ActionCreationContext;
import com.akkaserverless.javasdk.action.ActionOptions;
import com.akkaserverless.javasdk.action.ActionProvider;
import com.akkaserverless.javasdk.impl.action.ActionRouter;
import com.google.protobuf.Descriptors;
import com.google.protobuf.Empty;
import com.google.protobuf.EmptyProto;

import java.util.function.Function;

// This code is managed by Akka Serverless tooling.
// It will be re-generated to reflect any changes to your protobuf definitions.
// DO NOT EDIT

/**
* MyServiceActionProvider that defines how to register and create the action for
* the Protobuf service <code>MyService</code>.
*
* Should be used with the <code>register</code> method in {@link com.akkaserverless.javasdk.AkkaServerless}.
*/
public class MyServiceActionProvider implements ActionProvider<MyServiceAction> {

private final Function<ActionCreationContext, MyServiceAction> actionFactory;
private final ActionOptions options;

/** Factory method of MyServiceActionProvider */
public static MyServiceActionProvider of(Function<ActionCreationContext, MyServiceAction> actionFactory) {
return new MyServiceActionProvider(actionFactory, ActionOptions.defaults());
}

private MyServiceActionProvider(Function<ActionCreationContext, MyServiceAction> actionFactory, ActionOptions options) {
this.actionFactory = actionFactory;
this.options = options;
}

@Override
public final ActionOptions options() {
return options;
}

public final MyServiceActionProvider withOptions(ActionOptions options) {
return new MyServiceActionProvider(actionFactory, options);
}

@Override
public final Descriptors.ServiceDescriptor serviceDescriptor() {
return ServiceOuterClass.getDescriptor().findServiceByName("MyService");
}

@Override
public final MyServiceActionRouter newRouter(ActionCreationContext context) {
return new MyServiceActionRouter(actionFactory.apply(context));
}

@Override
public final Descriptors.FileDescriptor[] additionalDescriptors() {
return new Descriptors.FileDescriptor[] {
EmptyProto.getDescriptor(),
ServiceOuterClass.getDescriptor()
};
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.example.service;

import akka.NotUsed;
import akka.stream.javadsl.Source;
import com.akkaserverless.javasdk.action.Action.Effect;
import com.akkaserverless.javasdk.action.MessageEnvelope;
import com.akkaserverless.javasdk.impl.action.ActionRouter;
import com.google.protobuf.Empty;

// This code is managed by Akka Serverless tooling.
// It will be re-generated to reflect any changes to your protobuf definitions.
// DO NOT EDIT

public class MyServiceActionRouter extends ActionRouter<MyServiceAction> {

public MyServiceActionRouter(MyServiceAction actionBehavior) {
super(actionBehavior);
}

@Override
public Effect<?> handleUnary(String commandName, MessageEnvelope<Object> message) {
switch (commandName) {
case "simpleMethod":
return action()
.simpleMethod((ServiceOuterClass.MyRequest) message.payload());
default:
throw new ActionRouter.HandlerNotFound(commandName);
}
}

@Override
@SuppressWarnings("unchecked")
public Source<Effect<?>, NotUsed> handleStreamedOut(String commandName, MessageEnvelope<Object> message) {
switch (commandName) {
case "streamedOutputMethod":
return (Source<Effect<?>, NotUsed>)(Object) action()
.streamedOutputMethod((ServiceOuterClass.MyRequest) message.payload());
default:
throw new ActionRouter.HandlerNotFound(commandName);
}
}

@Override
public Effect<?> handleStreamedIn(String commandName, Source<MessageEnvelope<Object>, NotUsed> stream) {
switch (commandName) {
case "streamedInputMethod":
return action()
.streamedInputMethod(stream.map(el -> (ServiceOuterClass.MyRequest) el.payload()));
default:
throw new ActionRouter.HandlerNotFound(commandName);
}
}

@Override
@SuppressWarnings("unchecked")
public Source<Effect<?>, NotUsed> handleStreamed(String commandName, Source<MessageEnvelope<Object>, NotUsed> stream) {
switch (commandName) {
case "fullStreamedMethod":
return (Source<Effect<?>, NotUsed>)(Object) action()
.fullStreamedMethod(stream.map(el -> (ServiceOuterClass.MyRequest) el.payload()));
default:
throw new ActionRouter.HandlerNotFound(commandName);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.example;

import com.akkaserverless.javasdk.AkkaServerless;
import com.akkaserverless.javasdk.action.ActionCreationContext;
import com.example.service.MyServiceAction;
import com.example.service.MyServiceActionProvider;
import com.example.service.ServiceOuterClass;

import java.util.function.Function;

// This code is managed by Akka Serverless tooling.
// It will be re-generated to reflect any changes to your protobuf definitions.
// DO NOT EDIT

public final class AkkaServerlessFactory {

public static AkkaServerless withComponents(
Function<ActionCreationContext, MyServiceAction> createMyServiceAction) {
AkkaServerless akkaServerless = new AkkaServerless();
return akkaServerless
.register(MyServiceActionProvider.of(createMyServiceAction));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.example;

import com.akkaserverless.javasdk.DeferredCall;

// This code is managed by Akka Serverless tooling.
// It will be re-generated to reflect any changes to your protobuf definitions.
// DO NOT EDIT

/**
* Not intended for user extension, provided through generated implementation
*/
public interface Components {
MyServiceActionCalls myServiceAction();

interface MyServiceActionCalls {
DeferredCall<com.example.service.ServiceOuterClass.MyRequest, com.google.protobuf.Empty> simpleMethod(com.example.service.ServiceOuterClass.MyRequest myRequest);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.example;

import com.akkaserverless.javasdk.Context;
import com.akkaserverless.javasdk.DeferredCall;
import com.akkaserverless.javasdk.impl.DeferredCallImpl;
import com.akkaserverless.javasdk.impl.InternalContext;
import com.akkaserverless.javasdk.impl.MetadataImpl;

// This code is managed by Akka Serverless tooling.
// It will be re-generated to reflect any changes to your protobuf definitions.
// DO NOT EDIT

/**
* Not intended for direct instantiation, called by generated code, use Action.components() to access
*/
public final class ComponentsImpl implements Components {

private final InternalContext context;

public ComponentsImpl(Context context) {
this.context = (InternalContext) context;
}

private <T> T getGrpcClient(Class<T> serviceClass) {
return context.getComponentGrpcClient(serviceClass);
}

@Override
public Components.MyServiceActionCalls myServiceAction() {
return new MyServiceActionCallsImpl();
}

private final class MyServiceActionCallsImpl implements Components.MyServiceActionCalls {
@Override
public DeferredCall<com.example.service.ServiceOuterClass.MyRequest, com.google.protobuf.Empty> simpleMethod(com.example.service.ServiceOuterClass.MyRequest myRequest) {
return new DeferredCallImpl<>(
myRequest,
MetadataImpl.Empty(),
"com.example.service.MyService",
"simpleMethod",
() -> getGrpcClient(com.example.service.MyService.class).simpleMethod(myRequest)
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.example.service;

import akka.NotUsed;
import akka.stream.javadsl.Source;
import com.akkaserverless.javasdk.action.Action.Effect;
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.TestKitActionContext;
import com.example.service.MyServiceAction;
import com.example.service.ServiceOuterClass;
import com.google.protobuf.Empty;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;

// This code is managed by Akka Serverless tooling.
// It will be re-generated to reflect any changes to your protobuf definitions.
// DO NOT EDIT

public final class MyServiceActionTestKit {

private Function<ActionCreationContext, MyServiceAction> actionFactory;

private MyServiceAction createAction() {
MyServiceAction action = actionFactory.apply(new TestKitActionContext());
action._internalSetActionContext(Optional.of(new TestKitActionContext()));
return action;
};

public static MyServiceActionTestKit of(Function<ActionCreationContext, MyServiceAction> actionFactory) {
return new MyServiceActionTestKit(actionFactory);
}

private MyServiceActionTestKit(Function<ActionCreationContext, MyServiceAction> actionFactory) {
this.actionFactory = actionFactory;
}

private <E> ActionResult<E> interpretEffects(Effect<E> effect) {
return new ActionResultImpl(effect);
}

public ActionResult<Empty> simpleMethod(ServiceOuterClass.MyRequest myRequest) {
Effect<Empty> effect = createAction().simpleMethod(myRequest);
return interpretEffects(effect);
}

public Source<ActionResult<Empty>, akka.NotUsed> streamedOutputMethod(ServiceOuterClass.MyRequest myRequest) {
Source<Effect<Empty>, akka.NotUsed> effect = createAction().streamedOutputMethod(myRequest);
return effect.map(e -> interpretEffects(e));
}

public ActionResult<Empty> streamedInputMethod(Source<ServiceOuterClass.MyRequest, akka.NotUsed> myRequest) {
Effect<Empty> effect = createAction().streamedInputMethod(myRequest);
return interpretEffects(effect);
}

public Source<ActionResult<Empty>, akka.NotUsed> fullStreamedMethod(Source<ServiceOuterClass.MyRequest, akka.NotUsed> myRequest) {
Source<Effect<Empty>, akka.NotUsed> effect = createAction().fullStreamedMethod(myRequest);
return effect.map(e -> interpretEffects(e));
}

}
Loading

0 comments on commit b41ec26

Please sign in to comment.